Package ar.glyphsets

Source Code of ar.glyphsets.DynamicQuadTree$LeafQuad

package ar.glyphsets;

import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

import ar.Glyph;
import ar.Glyphset;
import ar.util.Util;


/**Explicit geometry, spatially arranged glyphset with dynamically growing extent.
*
* This class should be constructed using the "make" method, thus no constructor is exposed.
*
*
* In this quad tree, items appear in each node that they touch (e.g., multi-homed).
* Can split an existing node into sub-nodes or move "up" and make the root a sub-node with new siblings/parent.
* No items are held in intermediate nodes.
*
* This class can be used efficiently with the pixel serial or pixel parallel renderer.
*
* There are four types of nodes:
*   ** RootHolder is a proxy for a root node,
*      but enables the bounds of the tree to expand without the host program knowing.
*   ** InnerNode is a node that has no items in it, but has quadrants below it that may have items
*   ** LeafNode is a node that may have items but also may have one more set of nodes beneath it.
*      The items directly held by the leaf nodes are those that touch multiple of its children.
*   ** LeafQuad is a node that may have items but no children. All items held in a LeafQuad are
*      entirely contained by that LeafQuad.
*
* The LeafNode vs. LeafQuad split is made so the decision to further divide the tree
* can be flexible and efficient.  A LeafNode may only have LeafQuads as its children. 
*
* **/

public abstract class DynamicQuadTree<G,I> implements Glyphset<G,I> {
  /**Smallest quad that will be created.**/
  public static double MIN_DIM = .001d;
 
  /**Percentage of items that need to be uniquely assigned to a sub-quad for a split to occur.
   *
   * Without a load-factor, a quad will split once it hits capacity.
   * However, if all items touch all sub-quads, then the split may become an infinite loop.
   * This load-factor addresses how many items are uniquely into one sub-quad before
   * the split is done. 
   */
  public static double CROSS_LOAD_FACTOR = .25;
 
  /**How many items before splitting is considered.**/
  public static int LOADING = 10;
 
  /**Feather-factor for sub-quads.  Each sub-quad
   * overlaps with its neighbors slightly.  This prevents
   * items from falling between quads.
   */
  public static double FEATHER = MIN_DIM/4.0d;
 
  private static int NW = 0;
  private static int NE = 1;
  private static int SW = 2;
  private static int SE = 3;

  /**Structure to represent the bounds of the sub-quadrants of a node**/
  private static final class Subs {
    public final Rectangle2D[] quads = new Rectangle2D[4];
    public Subs (final Rectangle2D current) {
      double w = (current.getWidth()/2)+(2*FEATHER);
      double h = (current.getHeight()/2)+(2*FEATHER);
      quads[NW] = new Rectangle2D.Double(current.getX()-FEATHER,       current.getY()-FEATHER,w,h);
      quads[NE] = new Rectangle2D.Double(current.getCenterX()-FEATHER, current.getY()-FEATHER, w,h);
      quads[SW] = new Rectangle2D.Double(current.getX()-FEATHER,       current.getCenterY()-FEATHER, w,h);
      quads[SE] = new Rectangle2D.Double(current.getCenterX()-FEATHER, current.getCenterY()-FEATHER, w,h);
    }
  }
 
  /**How many items before exploring subdivisions.**/
  protected final Rectangle2D concernBounds;

  /**Construct a dynamic quad tree for the given value type.*/
  public static <G,I> DynamicQuadTree<G,I> make() {return new DynamicQuadTree.RootHolder<>();}

  protected DynamicQuadTree(Rectangle2D concernBounds) {
    this.concernBounds = concernBounds;
  }

  /**What space is this node responsible for?**/
  public Rectangle2D concernBounds() {return concernBounds;}

  /**Tight bounding of the items contained under this node.
   * Will always be equal to or smaller than concernBounds.
   * Where concernBounds is a statement of what may be,
   * bounds is a statement of what is.**/
  public abstract Rectangle2D bounds();
 
  /**Add an item to the node's sub-tree**/
  public abstract void add(Glyph<G,I> glyph);

  /**How many things are held in this sub-tree?**/
  public long size() {return items().size();}

  /**What are the items of the sub-tree?**/
  public Collection<Glyph<G,I> > items() {
    Collection<Glyph<G,I>> collector = new HashSet<>();
    items(collector);
    return collector;   
  }

  /**Efficiency method for collecting items.**/
  protected abstract void items(Collection<Glyph<G,I>> collector);


  /**What items in this sub-tree contain the passed point?**/ 
  public Collection<Glyph<G,I>> intersects(Rectangle2D pixel) {
    Collection<Glyph<G,I>> collector = new HashSet<Glyph<G,I>>();
    intersects(pixel, collector);
    return collector;
  }

  /**Efficiency method for collecting items touching a point**/
  protected abstract void intersects(Rectangle2D pixel, Collection<Glyph<G,I>> collector);

  protected boolean doSplit() {return false;}
 
  /**Convert the tree to a string where indentation indicates depth in tree.**/
  public abstract String toString(int indent);
  public String toString() {return toString(0);}


  protected static <G,V> DynamicQuadTree<G,V> addTo(DynamicQuadTree<G,V> target, final Glyph<G,V> item) {
    if (target.doSplit()) {target = new InnerNode<>((LeafNode<G,V>) target);}
     target.add(item);
    return target;
  }

  private static <G,V> Glyphset<G,V> subset(DynamicQuadTree<G,V>[] glyphs, int count, int segId) {
    if (count >= glyphs.length) {
      if (segId < glyphs.length) {return glyphs[segId];}
      return new EmptyGlyphset<>();
    } else {
      int stride = (glyphs.length/count);
      int low = stride*segId;
      int high = segId == count-1 ? glyphs.length : Math.min(low+stride, glyphs.length);
      if (low > high) {
        System.out.println("XXX");
      }
     
      DynamicQuadTree<G,V>[] subset = Arrays.copyOfRange(glyphs, low, high);
      return new InnerNode<G,V>(subset);
    }
  }
 
  @SuppressWarnings({ "unused", "rawtypes" })
  //Utility method, helpful in debugging tree splits
  private static String boundsReport(DynamicQuadTree<?,?> t) {
    if (t instanceof InnerNode) {
      InnerNode i = (InnerNode) t;
      return String.format("IB: %s\n\tNE: %s\n\tNW: %s\n\tSE: %s\n\tSW: %s\n",
          i.concernBounds,
          i.quads[NE].concernBounds, i.quads[NW].concernBounds, i.quads[SE].concernBounds, i.quads[SW].concernBounds);
    } else if (t instanceof RootHolder) {
      return String.format("RB: %s\n", t.concernBounds);
    } else if (t instanceof LeafNode) {
      LeafNode l = (LeafNode) t;
      return String.format("LB: %s\n\tNE: %s\n\tNW: %s\n\tSE: %s\n\tSW: %s\n",
          l.concernBounds,
          l.quads[NE].concernBounds, l.quads[NW].concernBounds, l.quads[SE].concernBounds, l.quads[SW].concernBounds);
    }
    return "Not a know type: " + t.getClass().getName();
  }

  /**The root node does not actually hold an items, it is to facilitate the "up" direction splits.
   * A node of this type is always the initial node of the tree.  Most operations are passed
   * through it to its only child.**/
  private static final class RootHolder<G,V> extends DynamicQuadTree<G,V> {
    private DynamicQuadTree<G,V> child;
   
    public RootHolder() {
      super(null);
      child = new LeafNode<G,V>(new Rectangle2D.Double(0,0,0,0));
    }

    public void add(Glyph<G,V> glyph) {
      Rectangle2D b = Util.boundOne(glyph.shape());

      if (!child.concernBounds().contains(b)) {
        if (child instanceof LeafNode) {
          //If the root is a leaf, then the tree has no depth, so we feel free to expand the root
          //to fit the data until the loading limit has been reached

          Rectangle2D newBounds = Util.bounds(b, child.bounds());
          DynamicQuadTree<G,V> newChild = new LeafNode<>(newBounds, (LeafNode<G,V>) child);
          this.child = newChild;
        } else {
          DynamicQuadTree<G,V> c = child;
          while (!c.concernBounds.contains(b)) {
            c = growUp((DynamicQuadTree.InnerNode<G,V>) c, b);
          }
          this.child = c;
        }
      }

      child = DynamicQuadTree.addTo(child, glyph);
    }
       
    /**Grow the tree so it covers more area than it does currently.
     * The strategies are based on heuristics and have not been evaluated
     * for optimality (only sufficiency).
     *
     * Strategies:
     *   (1) If the new thing touches the current bounds, the the current tree
     *   is the "center" of new tree.  Each quad of the current tree becomes
     *   a sub-quad of each of the new tree's quad.  If a current quad
     *   is a leaf, that leaf is expanded to become a new leaf in the new tree.
     *  
     *   (2) Otherwise,  the current tree becomes a quad in the new tree.
     *   Right and above are prioritized higher than left and below.
     * @return
     */
    private static final <G,V> InnerNode<G,V> growUp(DynamicQuadTree.InnerNode<G,V> current, Rectangle2D toward) {
      Rectangle2D currentBounds = current.concernBounds();
      int outCode = currentBounds.outcode(toward.getX(), toward.getY());
     
      if (toward.intersects(currentBounds)
          || outCode ==0) {
        //If the new glyph touches the current bounds, then grow with the current data in the center.
        Rectangle2D newBounds = new Rectangle2D.Double(
            currentBounds.getX()-currentBounds.getWidth()/2.0d,
            currentBounds.getY()-currentBounds.getHeight()/2.0d,
            currentBounds.getWidth()*2,
            currentBounds.getHeight()*2);
       
        //The following checks prevent empty nodes from proliferating as you split up. 
        //Leaf nodes in the old tree are rebounded for the new tree. 
        //Non-leaf nodes are replaced with a quad of nodes
        InnerNode<G,V> newChild = new InnerNode<G,V>(newBounds);
        if (current.quads[NE] instanceof InnerNode) {
          newChild.quads[NE] =new InnerNode<G,V>(newChild.quads[NE].concernBounds());
          ((InnerNode<G,V>) newChild.quads[NE]).quads[SW] = current.quads[NE];
        } else if (!current.quads[NE].isEmpty()) {
          newChild.quads[NE] = new LeafNode<G,V>(newChild.quads[NE].concernBounds(), (LeafNode<G,V>) current.quads[NE]);
        }
 
        if (current.quads[NW] instanceof InnerNode) {
          newChild.quads[NW] = new InnerNode<G,V>(newChild.quads[NW].concernBounds());
          ((InnerNode<G,V>) newChild.quads[NW]).quads[SE] = current.quads[NW];
        } else if (!current.quads[NW].isEmpty()) {
          newChild.quads[NW] = new LeafNode<G,V>(newChild.quads[NW].concernBounds(), (LeafNode<G,V>) current.quads[NW]);
        }
 
        if (current.quads[SW] instanceof InnerNode) {
          newChild.quads[SW] = new InnerNode<G,V>(newChild.quads[SW].concernBounds());
          ((InnerNode<G,V>) newChild.quads[SW]).quads[NE] = current.quads[SW];
        } else if (!current.quads[SW].isEmpty()) {
          newChild.quads[SW] = new LeafNode<G,V>(newChild.quads[SW].concernBounds(), (LeafNode<G,V>) current.quads[SW]);
        }
 
        if (current.quads[SE] instanceof InnerNode) {
          newChild.quads[SE] = new InnerNode<G,V>(newChild.quads[SE].concernBounds());
          ((InnerNode<G,V>) newChild.quads[SE]).quads[NW] = current.quads[SE];
        } else if (!current.quads[SE].isEmpty()) {
          newChild.quads[SE] = new LeafNode<G,V>(newChild.quads[SE].concernBounds(), (LeafNode<G,V>) current.quads[SE]);
        }
        return newChild;
      } else {
        double x,y;
        int replace;
       
        if ((outCode & Rectangle2D.OUT_RIGHT) == Rectangle2D.OUT_RIGHT
            || (outCode & Rectangle2D.OUT_TOP) == Rectangle2D.OUT_TOP) {
          x = currentBounds.getX();
          y = currentBounds.getY()-currentBounds.getHeight();
          replace = SW;
         
        } else   if ((outCode & Rectangle2D.OUT_LEFT) == Rectangle2D.OUT_LEFT
            || (outCode & Rectangle2D.OUT_BOTTOM) == Rectangle2D.OUT_BOTTOM) {
          x = currentBounds.getX()-currentBounds.getWidth();
          y = currentBounds.getY();
          replace = NE;
         
        } else {throw new RuntimeException("Growing up encountered unexpected out-code:" + outCode);}

        Rectangle2D newBounds =new Rectangle2D.Double(x,y,currentBounds.getWidth()*2.0d,currentBounds.getHeight()*2.0d);
        InnerNode<G,V> newChild = new InnerNode<G,V>(newBounds);
        newChild.quads[replace] = current;
        return newChild;
      }
    }

    @Override public boolean isEmpty() {return child.isEmpty();}
    @Override public Rectangle2D concernBounds() {return child.concernBounds();}
    @Override public Rectangle2D bounds() {return child.bounds();}
    @Override public void items(Collection<Glyph<G,V>> collector) {child.items(collector);}
    @Override public void intersects(Rectangle2D pixel, Collection<Glyph<G,V>> collector) {child.intersects(pixel, collector);}
    @Override public String toString(int indent) {return child.toString(indent);}
    @Override public Glyphset<G,V> segmentAt(int count, int segId) {return child.segmentAt(count, segId);}
    @Override public Iterator<Glyph<G,V>> iterator() {return items().iterator();}
  }

  private static final class InnerNode<G,V> extends DynamicQuadTree<G,V> {
    private final DynamicQuadTree<G,V>[] quads;

    /**Create a new InnerNode from an existing leaf node.
     * The quads of the leaf can be copied directly to the children of this node.
     * Only the spanning items of the leaf need to go through the regular add procedure.
     */
    private InnerNode(LeafNode<G,V> source) {
      this(source.concernBounds);
      for (int i=0; i<quads.length;i++) {
        for (Glyph<G,V> g: source.quads[i].items) {quads[i].add(g);}
      }
      for (Glyph<G,V> g:source.spanningItems) {add(g);}
    }
   
    @SuppressWarnings("unchecked")
    private InnerNode(Rectangle2D concernBounds) {
      super(concernBounds);
      quads = new DynamicQuadTree[4];
      Subs subs = new Subs(concernBounds);
      for (int i=0; i< subs.quads.length; i++) {
        quads[i] = new DynamicQuadTree.LeafNode<G,V>(subs.quads[i]);
      }
    }
    private InnerNode(DynamicQuadTree<G,V>[] parts) {
      super(null);
      this.quads = parts;
    }

    public void add(Glyph<G,V> glyph) {
      boolean added = false;
      Rectangle2D glyphBounds = Util.boundOne(glyph.shape());

      for (int i=0; i<quads.length; i++) {
        DynamicQuadTree<G,V> quad = quads[i];
        if (quad.concernBounds.intersects(glyphBounds)) {
          quads[i] = addTo(quad, glyph);
          added = true;
        }
      }

      if (!added && concernBounds.outcode(glyphBounds.getX(), glyphBounds.getY()) !=0) {
        throw new RuntimeException(String.format("Did not add glyph bounded %s to node with concern %s", glyphBounds, concernBounds));
      }
    }

    @Override
    public void intersects(Rectangle2D pixel, Collection<Glyph<G,V>> collector) {
      for (DynamicQuadTree<G,V> q: quads) {
        if (q.concernBounds.intersects(pixel)) {q.intersects(pixel, collector);}
      }
    }

    @Override
    public boolean isEmpty() {
      for (DynamicQuadTree<G,V> q: quads) {if (!q.isEmpty()) {return false;}}
      return true;
    }

    @Override
    public void items(Collection<Glyph<G,V>> collector) {
      for (DynamicQuadTree<G,V> q: quads) {q.items(collector);}
    }

    @Override
    public Rectangle2D bounds() {
      final Rectangle2D[] bounds = new Rectangle2D[quads.length];
      for (int i=0; i<bounds.length; i++) {
        bounds[i] = quads[i].bounds();
      }
      return Util.bounds(bounds);
    }
   
    @Override
    public String toString(int indent) {
      StringBuilder b = new StringBuilder();
      for (DynamicQuadTree<G,V> q: quads) {b.append(q.toString(indent+1));}
      return String.format("%sNode: %d items\n", Util.indent(indent), size()) + b.toString();
    }

    @Override
    public Glyphset<G,V> segmentAt(int count, int segId) {
      return DynamicQuadTree.subset(quads, count, segId);
    }
   
    @Override
    public Iterator<Glyph<G,V>> iterator() {return items().iterator();}
  }
 
  private static final class LeafNode<G,V> extends DynamicQuadTree<G,V> {
   
    @SuppressWarnings("unchecked")
    //Used to iterate over all items (quads and spanning items) uniformly; useful in segmentation
    private final LeafQuad<G,V>[] parts = new LeafQuad[5];
   
    @SuppressWarnings("unchecked")
    private final LeafQuad<G,V>[] quads = new LeafQuad[4];

    private final LeafQuad<G,V> spanningItems;
    private int size=0;

    private LeafNode(Rectangle2D concernBounds) {
      this(concernBounds, new ArrayList<Glyph<G,V>>());
     
    }

    //Re-bounding version.
    //WARNING: This introduces data sharing and should only be done if old will immediately be destroyed
    private LeafNode(Rectangle2D concernBounds, LeafNode<G,V> old) {
      this(concernBounds, old.items());
    }

    private LeafNode(Rectangle2D concernBounds, Collection<Glyph<G,V>> glyphs) {
      super(concernBounds);
      spanningItems = new LeafQuad<>(this.concernBounds);
      Subs subs = new Subs(concernBounds);
      for (int i=0; i< subs.quads.length; i++) {
        quads[i] = new DynamicQuadTree.LeafQuad<>(subs.quads[i]);
        parts[i] = quads[i];
      }
      parts[parts.length-1] = spanningItems;
      for (Glyph<G,V> g: glyphs) {add(g);}
    }

    public void add(Glyph<G,V> glyph) {
      boolean[] hits = new boolean[quads.length];
      int totalHits=0;

      Rectangle2D glyphBounds = Util.boundOne(glyph.shape());
      for (int i=0; i<quads.length; i++) {
        LeafQuad<G,V> quad = quads[i];
        if (quad.concernBounds.intersects(glyphBounds)) {hits[i]=true; totalHits++;}
        if (totalHits>2) {break;}
      }
     
      if (totalHits>1) {spanningItems.add(glyph);}
      else {for (int i=0; i<hits.length;i++) {if (hits[i]) {quads[i].add(glyph);}}}
      size++;

      if (totalHits ==0 && concernBounds.outcode(glyphBounds.getX(), glyphBounds.getY()) !=0) {
        throw new RuntimeException(String.format("Did not add glyph bounded %s to node with concern %s", glyphBounds, concernBounds));
      }
    }

   
    @Override public long size() {return size;}
   
    @Override
    public String toString(int indent) {
      return String.format("%sLeafNode: %d items (%s spanning items)\n", Util.indent(indent), size(), spanningItems.size());
   
   
    /**Should this leaf become an inner node?  Yes...but only if it would help one of the quads.**/
    protected boolean doSplit() {
      if (size < LOADING && concernBounds.getWidth() < MIN_DIM) {return false;}
      for (LeafQuad<G,V> q: quads) {
        if (q.size() > LOADING && (q.size()/(spanningItems.size()+1) > CROSS_LOAD_FACTOR)) {return true;}
      }
      return false;
    }
   
    @Override
    public void intersects(Rectangle2D pixel, Collection<Glyph<G,V>> collector) {
      for (DynamicQuadTree<G,V> q: quads) {
        if (q.concernBounds.intersects(pixel)) {q.intersects(pixel, collector); break;}
      }
      for (Glyph<G,V> g:spanningItems) {if (Util.intersects(pixel, g.shape())) {collector.add(g);}}
    }
   
    @Override
    public boolean isEmpty() {
      for (DynamicQuadTree<G,V> q: quads) {if (!q.isEmpty()) {return false;}}
      return spanningItems.size() == 0;
    }

   
    //Copy
    @Override
    public Rectangle2D bounds() {
      final Rectangle2D[] bounds = new Rectangle2D[quads.length+1];
      for (int i=0; i<quads.length; i++) {
        bounds[i] = quads[i].bounds();
      }
      bounds[quads.length] = Util.bounds(spanningItems);
      return Util.bounds(bounds);
    }

    //Copy
    @Override
    public void items(Collection<Glyph<G,V>> collector) {
      collector.addAll(spanningItems.items);
      for (DynamicQuadTree<G,V> q: quads) {q.items(collector);}
    }
   
    @Override
    public Glyphset<G,V> segmentAt(int count, int segId) {return DynamicQuadTree.subset(parts, count, segId);
    @Override  public Iterator<Glyph<G,V>> iterator() {return items().iterator();}

  } 

  /**Sub-leaf is a quadrant of a leaf.
   * It represents a set of items that would be entirely in one leaf quadrant if the leaf were to split.
   * This class exists so leaf-split decisions can be made efficiently.
   */
  private static final class LeafQuad<G,V> extends DynamicQuadTree<G,V> implements Glyphset.RandomAccess<G,V> {
    private final List<Glyph<G,V>> items;
   
    protected LeafQuad(Rectangle2D concernBounds) {
      super (concernBounds);
      items = new ArrayList<>(LOADING);
    }

    //Assumes the geometry check was done by the parent
    @Override
    public void add(Glyph<G,V> glyph) {
      items.add(glyph);
    }

    @Override public Rectangle2D concernBounds() {return concernBounds;}
    @Override public boolean isEmpty() {return items.isEmpty();}
    @Override public List<Glyph<G,V>> items() {return items;}
    @Override public String toString(int level) {return Util.indent(level) + "LeafQuad: " + items.size() + " items\n";}
    @Override public Rectangle2D bounds() {return Util.bounds(items);}
    @Override protected void items(Collection<Glyph<G,V>> collector) {collector.addAll(items);}
   
    @Override
    protected void intersects(Rectangle2D pixel, Collection<Glyph<G,V>> collector) {
      for (Glyph<G,V> g: items) {if (Util.intersects(pixel, g.shape())) {collector.add(g);}}
    }

    @Override
    public Glyphset<G,V> segmentAt(int count, int segId) {
      if (segId ==0) {return this;}
      return new EmptyGlyphset<>();
    }
    @Override public Iterator<Glyph<G,V>> iterator() {return items().iterator();}
    @Override public Glyph<G,V> get(long l) {return items.get((int) l);}
  }
 
}
TOP

Related Classes of ar.glyphsets.DynamicQuadTree$LeafQuad

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.