JesseScott_kwilt/kwilt/HYPE.pde
public static abstract class HBehavior extends HNode<HBehavior> { 
  protected HBehaviorRegistry _registry; 
  public HBehavior register() { 
    H.behaviors().register(this); 
    return this;
  } 
  public HBehavior unregister() { 
    H.behaviors().unregister(this); 
    return this;
  } 
  public boolean poppedOut() { 
    return _registry == null;
  } 
  public void popOut() { 
    super.popOut(); 
    _registry = null;
  } 
  public void swapLeft() { 
    if (_prev._prev == null) return; 
    super.swapLeft();
  } 
  public void putAfter(HBehavior dest) { 
    if (dest._registry == null) return; 
    super.putAfter(dest); 
    _registry = dest._registry;
  } 
  public void putBefore(HBehavior dest) { 
    if (dest._registry == null) return; 
    super.putBefore(dest); 
    _registry = dest._registry;
  } 
  public void replaceNode(HBehavior target) { 
    super.replaceNode(target); 
    _registry = target._registry; 
    target._registry = null;
  } 
  public abstract void runBehavior(PApplet app);
} 
public static class HBehaviorRegistry { 
  private HBehaviorSentinel _firstSentinel; 
  public HBehaviorRegistry() { 
    _firstSentinel = new HBehaviorSentinel(this);
  } 
  public boolean isRegistered(HBehavior b) { 
    return (b._registry != null && b._registry.equals(this));
  } 
  public void register(HBehavior b) { 
    if (b.poppedOut()) b.putAfter(_firstSentinel);
  } 
  public void unregister(HBehavior b) { 
    if (isRegistered(b)) b.popOut();
  } 
  public void runAll(PApplet app) { 
    HBehavior n = _firstSentinel.next(); 
    while (n != null) { 
      n.runBehavior(app); 
      n = n.next();
    }
  } 
  public static class HBehaviorSentinel extends HBehavior { 
    public HBehaviorSentinel(HBehaviorRegistry r) { 
      _registry = r;
    } 
    public void runBehavior(PApplet app) {
    }
  }
} 
public static abstract class HTrigger extends HBehavior { 
  protected HCallback _callback; 
  public HTrigger() { 
    register(); 
    _callback = HConstants.NOP;
  } 
  public HTrigger callback(HCallback cb) { 
    _callback = (cb==null)? HConstants.NOP : cb; 
    return this;
  } 
  public HCallback callback() { 
    return _callback;
  }
} 
public static class HLinkedHashSet<T> extends HLinkedList<T> { 
  private HashMap<T, HLinkedListNode<T>> nodeMap; 
  public HLinkedHashSet() { 
    nodeMap = new HashMap<T, HLinkedListNode<T>>();
  } 
  public boolean remove(T content) { 
    HLinkedListNode<T> node = nodeMap.get(content); 
    if (node==null) return false; 
    unregister(content); 
    node.popOut(); 
    --_size; 
    return true;
  } 
  public boolean add(T content) { 
    return contains(content)? false : super.add(content);
  } 
  public boolean push(T content) { 
    return contains(content)? false : super.push(content);
  } 
  public boolean insert(T content, int index) { 
    return contains(content)? false : super.insert(content, index);
  } 
  public T pull() { 
    return unregister(super.pull());
  } 
  public T pop() { 
    return unregister(super.pop());
  } 
  public T removeAt(int index) { 
    return unregister(super.removeAt(index));
  } 
  public void removeAll() { 
    while (_size > 0) pop();
  } 
  public boolean contains(T obj) { 
    return nodeMap.get(obj) != null;
  } 
  protected HLinkedListNode<T> register(T obj) { 
    HLinkedListNode<T> node = new HLinkedListNode<T>(obj); 
    nodeMap.put(obj, node); 
    return node;
  } 
  protected T unregister(T obj) { 
    nodeMap.remove(obj); 
    return obj;
  }
} 
public static class HLinkedList<T> implements Iterable<T> { 
  protected HLinkedListNode<T> _firstSentinel, _lastSentinel; 
  protected int _size; 
  public HLinkedList() { 
    _firstSentinel = new HLinkedListNode<T>(null); 
    _lastSentinel = new HLinkedListNode<T>(null); 
    _lastSentinel.putAfter(_firstSentinel);
  } 
  public T first() { 
    return _firstSentinel._next._content;
  } 
  public T last() { 
    return _lastSentinel._prev._content;
  } 
  public T get(int index) { 
    HLinkedListNode<T> n = nodeAt(index); 
    return (n==null)? null : n._content;
  } 
  public boolean push(T content) { 
    if (content==null) return false; 
    register(content).putAfter(_firstSentinel); 
    ++_size; 
    return true;
  } 
  public boolean add(T content) { 
    if (content==null) return false; 
    register(content).putBefore(_lastSentinel); 
    ++_size; 
    return true;
  } 
  public boolean insert(T content, int index) { 
    if (content==null) return false; 
    HLinkedListNode<T> n = (index==_size)? _lastSentinel : nodeAt(index); 
    if (n==null) return false; 
    register(content).putBefore(n); 
    ++_size; 
    return true;
  } 
  public T pop() { 
    HLinkedListNode<T> firstNode = _firstSentinel._next; 
    if (firstNode._content != null) { 
      firstNode.popOut(); 
      --_size;
    } 
    return firstNode._content;
  } 
  public T pull() { 
    HLinkedListNode<T> lastNode = _lastSentinel._prev; 
    if (lastNode._content != null) { 
      lastNode.popOut(); 
      --_size;
    } 
    return lastNode._content;
  } 
  public T removeAt(int index) { 
    HLinkedListNode<T> n = nodeAt(index); 
    if (n==null) return null; 
    n.popOut(); 
    --_size; 
    return n._content;
  } 
  public void removeAll() { 
    _lastSentinel.putAfter(_firstSentinel); 
    _size = 0;
  } 
  public int size() { 
    return _size;
  } 
  public boolean inRange(int index) { 
    return (0 <= index) && (index < _size);
  } 
  public HLinkedListIterator<T> iterator() { 
    return new HLinkedListIterator<T>(this);
  } 
  protected HLinkedListNode<T> nodeAt(int i) { 
    int ri; 
    if (i<0) { 
      ri = -i; 
      i += _size;
    } 
    else { 
      ri = _size-i;
    } 
    if (!inRange(i)) { 
      HWarnings.warn("Out of Range: "+i, "HLinkedList.nodeAt()", null); 
      return null;
    } 
    HLinkedListNode<T> node; 
    if (ri < i) { 
      node = _lastSentinel._prev; 
      while (--ri > 0) node = node._prev;
    } 
    else { 
      node = _firstSentinel._next; 
      while (i-- > 0) node = node._next;
    } 
    return node;
  } 
  protected HLinkedListNode<T> register(T obj) { 
    return new HLinkedListNode<T>(obj);
  } 
  public static class HLinkedListNode<U> extends HNode<HLinkedListNode<U>> { 
    private U _content; 
    public HLinkedListNode(U nodeContent) { 
      _content = nodeContent;
    } 
    public U content() { 
      return _content;
    }
  } 
  public static class HLinkedListIterator<U> implements Iterator<U> { 
    private HLinkedList<U> list; 
    private HLinkedListNode<U> n1, n2; 
    public HLinkedListIterator(HLinkedList<U> parent) { 
      list = parent; 
      n1 = list._firstSentinel._next; 
      if (n1 != null) n2 = n1._next;
    } 
    public boolean hasNext() { 
      return (n1._content != null);
    } 
    public U next() { 
      U content = n1._content; 
      n1 = n2; 
      if (n2 != null) n2 = n2._next; 
      return content;
    } 
    public void remove() { 
      if (n1._content != null) { 
        n1.popOut(); 
        --list._size;
      }
    }
  }
} 
public static abstract class HNode<T extends HNode<T>> { 
  protected T _prev, _next; 
  public T prev() { 
    return _prev;
  } 
  public T next() { 
    return _next;
  } 
  public boolean poppedOut() { 
    return (_prev==null) && (_next==null);
  } 
  public void popOut() { 
    if (_prev!=null) _prev._next = _next; 
    if (_next!=null) _next._prev = _prev; 
    _prev = _next = null;
  } 
  public void putBefore(T dest) { 
    if (dest==null || dest.equals(this)) return; 
    if (!poppedOut()) popOut(); 
    T p = dest._prev; 
    if (p!=null) p._next = (T) this; 
    _prev = p; 
    _next = dest; 
    dest._prev = (T) this;
  } 
  public void putAfter(T dest) { 
    if (dest==null || dest.equals(this)) return; 
    if (!poppedOut()) popOut(); 
    T n = dest.next(); 
    dest._next = (T) this; 
    _prev = dest; 
    _next = n; 
    if (n!=null) n._prev = (T) this;
  } 
  public void replaceNode(T dest) { 
    if (dest==null || dest.equals(this)) return; 
    if (!poppedOut()) popOut(); 
    T p = dest._prev; 
    T n = dest._next; 
    dest._prev = dest._next = null; 
    _prev = p; 
    _next = n;
  } 
  public void swapLeft() { 
    if (_prev==null) return; 
    T pairPrev = _prev._prev; 
    T pairNext = _next; 
    _next = _prev; 
    _prev._prev = (T) this; 
    _prev._next = pairNext; 
    if (pairNext != null) pairNext._prev = _prev; 
    _prev = pairPrev; 
    if (pairPrev != null) pairPrev._next = (T) this;
  } 
  public void swapRight() { 
    if (_next==null) return; 
    T pairPrev = _prev; 
    T pairNext = _next._next; 
    _next._next = (T) this; 
    _prev = _next; 
    _next._prev = pairPrev; 
    if (pairPrev != null) pairPrev._next = _next; 
    _next = pairNext; 
    if (pairNext != null) pairNext._prev = (T) this;
  }
} 
public static interface HColorist { 
  public HColorist fillOnly(); 
  public HColorist strokeOnly(); 
  public HColorist fillAndStroke(); 
  public boolean appliesFill(); 
  public boolean appliesStroke(); 
  public HDrawable applyColor(HDrawable drawable);
} 
public static abstract class HDrawable extends HNode<HDrawable> implements HDirectable, HHittable, Iterable<HDrawable> { 
  public static final int DEFAULT_FILL = 0xFFFFFFFF; 
  public static final int DEFAULT_STROKE = 0xFF000000; 
  public static final int DEFAULT_WIDTH = 100; 
  public static final int DEFAULT_HEIGHT = 100; 
  public static final byte BITMASK_PROPORTIONAL = 1; 
  public static final byte BITMASK_TRANSFORMS_CHILDREN = 2; 
  public static final byte BITMASK_STYLES_CHILDREN = 4; 
  public static final byte BITMASK_ROTATES_CHILDREN = 8; 
  protected HDrawable _parent; 
  protected HDrawable _firstChild; 
  protected HDrawable _lastChild; 
  protected HBundle _extras; 
  protected float _x; 
  protected float _y; 
  protected float _z; 
  protected float _anchorU; 
  protected float _anchorV; 
  protected float _width; 
  protected float _height; 
  protected float _rotationXRad; 
  protected float _rotationYRad; 
  protected float _rotationZRad; 
  protected float _strokeWeight; 
  protected float _alphaPc; 
  protected int _numChildren; 
  protected int _fill; 
  protected int _stroke; 
  protected int _strokeCap; 
  protected int _strokeJoin; 
  protected byte _flags; 
  public HDrawable() { 
    _alphaPc = 1; 
    _fill = DEFAULT_FILL; 
    _stroke = DEFAULT_STROKE; 
    _strokeCap = PConstants.ROUND; 
    _strokeJoin = PConstants.MITER; 
    _strokeWeight = 1; 
    _width = DEFAULT_WIDTH; 
    _height = DEFAULT_HEIGHT;
  } 
  public void copyPropertiesFrom(HDrawable other) { 
    _x = other._x; 
    _y = other._y; 
    _anchorU = other._anchorU; 
    _anchorV = other._anchorV; 
    _width = other._width; 
    _height = other._height; 
    _rotationZRad = other._rotationZRad; 
    _alphaPc = other._alphaPc; 
    _strokeWeight = other._strokeWeight; 
    _fill = other._fill; 
    _stroke = other._stroke; 
    _strokeCap = other._strokeCap; 
    _strokeJoin = other._strokeJoin;
  } 
  public abstract HDrawable createCopy(); 
  public boolean invalidChild(HDrawable destParent) { 
    if (destParent == null) return true; 
    if (destParent.equals(this)) return true; 
    return false;
  } 
  private boolean invalidDest(HDrawable dest, String warnLoc) { 
    String warnType; 
    String warnMsg; 
    if ( dest == null ) { 
      warnType = "Null Destination"; 
      warnMsg = HWarnings.NULL_ARGUMENT;
    } 
    else if ( dest._parent == null ) { 
      warnType = "Invalid Destination"; 
      warnMsg = HWarnings.INVALID_DEST;
    } 
    else if ( dest._parent.equals(this) ) { 
      warnType = "Recursive Child"; 
      warnMsg = HWarnings.CHILDCEPTION;
    } 
    else if ( dest.equals(this) ) { 
      warnType = "Invalid Destination"; 
      warnMsg = HWarnings.DESTCEPTION;
    } 
    else return false; 
    HWarnings.warn(warnType, warnLoc, warnMsg); 
    return true;
  } 
  public boolean poppedOut() { 
    return (_parent == null);
  } 
  public void popOut() { 
    if (_parent == null) return; 
    if (_prev == null) _parent._firstChild = _next; 
    if (_next == null) _parent._lastChild = _prev; 
    --_parent._numChildren; 
    _parent = null; 
    super.popOut();
  } 
  public void putBefore(HDrawable dest) { 
    if (invalidDest(dest, "HDrawable.putBefore()")) return; 
    popOut(); 
    super.putBefore(dest); 
    _parent = dest._parent; 
    if (_prev == null) _parent._firstChild = this; 
    ++_parent._numChildren;
  } 
  public void putAfter(HDrawable dest) { 
    if (invalidDest(dest, "HDrawable.putAfter()")) return; 
    popOut(); 
    super.putAfter(dest); 
    _parent = dest._parent; 
    if (_next == null) _parent._lastChild = this; 
    ++_parent._numChildren;
  } 
  public void swapLeft() { 
    boolean isLast = (_next == null); 
    super.swapLeft(); 
    if (_prev == null) _parent._firstChild = this; 
    if (_next != null && isLast) _parent._lastChild = _next;
  } 
  public void swapRight() { 
    boolean isFirst = (_prev == null); 
    super.swapRight(); 
    if (_next == null) _parent._lastChild = this; 
    if (_prev != null && isFirst) _parent._firstChild = _prev;
  } 
  public void replaceNode(HDrawable dest) { 
    if (invalidDest(dest, "HDrawable.replaceNode()")) return; 
    super.replaceNode(dest); 
    _parent = dest._parent; 
    dest._parent = null; 
    if (_prev == null) _parent._firstChild = this; 
    if (_next == null) _parent._lastChild = this;
  } 
  public HDrawable parent() { 
    return _parent;
  } 
  public HDrawable firstChild() { 
    return _firstChild;
  } 
  public HDrawable lastChild() { 
    return _lastChild;
  } 
  public boolean parentOf(HDrawable d) { 
    return (d != null) && (d._parent != null) && (d._parent.equals(this));
  } 
  public int numChildren() { 
    return _numChildren;
  } 
  public HCanvas add(HCanvas child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HEllipse add(HEllipse child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HGroup add(HGroup child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HImage add(HImage child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HPath add(HPath child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HRect add(HRect child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HShape add(HShape child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HText add(HText child) { 
    add((HDrawable) child); 
    return child;
  } 
  public HDrawable add(HDrawable child) { 
    if (child == null) { 
      HWarnings.warn("An Empty Child", "HDrawable.add()", HWarnings.NULL_ARGUMENT);
    } 
    else if ( child.invalidChild(this) ) { 
      HWarnings.warn("Invalid Child", "HDrawable.add()", HWarnings.INVALID_CHILD);
    } 
    else if ( !parentOf(child) ) { 
      if (_lastChild == null) { 
        _firstChild = _lastChild = child; 
        child.popOut(); 
        child._parent = this; 
        ++_numChildren;
      } 
      else child.putAfter(_lastChild);
    } 
    return child;
  } 
  public HCanvas remove(HCanvas child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HEllipse remove(HEllipse child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HGroup remove(HGroup child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HImage remove(HImage child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HPath remove(HPath child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HRect remove(HRect child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HShape remove(HShape child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HText remove(HText child) { 
    remove((HDrawable) child); 
    return child;
  } 
  public HDrawable remove(HDrawable child) { 
    if ( parentOf(child) ) child.popOut(); 
    else HWarnings.warn("Not a Child", "HDrawable.remove()", null); 
    return child;
  } 
  public HDrawableIterator iterator() { 
    return new HDrawableIterator(this);
  } 
  public HDrawable loc(float newX, float newY) { 
    _x = newX; 
    _y = newY; 
    return this;
  } 
  public HDrawable loc(float newX, float newY, float newZ) { 
    _x = newX; 
    _y = newY; 
    _z = newZ; 
    return this;
  } 
  public HDrawable loc(PVector pt) { 
    _x = pt.x; 
    _y = pt.y; 
    _z = pt.z; 
    return this;
  } 
  public PVector loc() { 
    return new PVector(_x, _y, _z);
  } 
  public HDrawable x(float newX) { 
    _x = newX; 
    return this;
  } 
  public float x() { 
    return _x;
  } 
  public HDrawable y(float newY) { 
    _y = newY; 
    return this;
  } 
  public float y() { 
    return _y;
  } 
  public HDrawable z(float newZ) { 
    _z = newZ; 
    return this;
  } 
  public float z() { 
    return _z;
  } 
  public HDrawable move(float dx, float dy) { 
    _x += dx; 
    _y += dy; 
    return this;
  } 
  public HDrawable move(float dx, float dy, float dz) { 
    _x += dx; 
    _y += dy; 
    _z += dz; 
    return this;
  } 
  public HDrawable locAt(int where) { 
    if (_parent!=null) { 
      if (HMath.hasBits(where, HConstants.CENTER_X)) { 
        _x = _parent.width()/2 - _parent.anchorX();
      } 
      else if (HMath.hasBits(where, HConstants.LEFT)) { 
        _x = -_parent.anchorX();
      } 
      else if (HMath.hasBits(where, HConstants.RIGHT)) { 
        _x = _parent.width() - _parent.anchorX();
      } 
      if (HMath.hasBits(where, HConstants.CENTER_Y)) { 
        _y = _parent.height()/2 - _parent.anchorY();
      } 
      else if (HMath.hasBits(where, HConstants.TOP)) { 
        _y = -_parent.anchorY();
      } 
      else if (HMath.hasBits(where, HConstants.BOTTOM)) { 
        _y = _parent.height() - _parent.anchorY();
      }
    } 
    return this;
  } 
  public HDrawable anchor(float pxX, float pxY) { 
    return anchorX(pxX).anchorY(pxY);
  } 
  public HDrawable anchor(PVector pt) { 
    return anchor(pt.x, pt.y);
  } 
  public PVector anchor() { 
    return new PVector( anchorX(), anchorY() );
  } 
  public HDrawable anchorX(float pxX) { 
    _anchorU = pxX / (_width==0? 100 : _width); 
    return this;
  } 
  public float anchorX() { 
    return _width * _anchorU;
  } 
  public HDrawable anchorY(float pxY) { 
    _anchorV = pxY / (_height==0? 100 : _height); 
    return this;
  } 
  public float anchorY() { 
    return _height * _anchorV;
  } 
  public HDrawable anchorUV(float u, float v) { 
    return anchorU(u).anchorV(v);
  } 
  public PVector anchorUV() { 
    return new PVector(_anchorU, _anchorV);
  } 
  public HDrawable anchorU(float u) { 
    _anchorU = u; 
    return this;
  } 
  public float anchorU() { 
    return _anchorU;
  } 
  public HDrawable anchorV(float v) { 
    _anchorV = v; 
    return this;
  } 
  public float anchorV() { 
    return _anchorV;
  } 
  public HDrawable anchorAt(int where) { 
    if (HMath.hasBits(where, HConstants.CENTER_X)) _anchorU = 0.5f; 
    else if (HMath.hasBits(where, HConstants.LEFT)) _anchorU = 0; 
    else if (HMath.hasBits(where, HConstants.RIGHT)) _anchorU = 1; 
    if (HMath.hasBits(where, HConstants.CENTER_Y)) _anchorV = 0.5f; 
    else if (HMath.hasBits(where, HConstants.TOP)) _anchorV = 0; 
    else if (HMath.hasBits(where, HConstants.BOTTOM)) _anchorV = 1; 
    return this;
  } 
  public HDrawable size(float w, float h) { 
    onResize( _width, _height, _width=w, _height=h ); 
    return this;
  } 
  public HDrawable size(float s) { 
    return size(s, s);
  } 
  public PVector size() { 
    return new PVector(_width, _height);
  } 
  public HDrawable width(float w) { 
    onResize( _width, _height, _width=w, _height ); 
    return this;
  } 
  public float width() { 
    return _width;
  } 
  public HDrawable height(float h) { 
    onResize( _width, _height, _width, _height=h ); 
    return this;
  } 
  public float height() { 
    return _height;
  } 
  public HDrawable scale(float s) { 
    return size(_width*s, _height*s);
  } 
  public HDrawable scale(float sw, float sh) { 
    return size(_width*sw, _height*sh);
  } 
  public HDrawable proportional(boolean b) { 
    _flags = HMath.setBits(_flags, BITMASK_PROPORTIONAL, b); 
    return this;
  } 
  public boolean proportional() { 
    return HMath.hasBits(_flags, BITMASK_PROPORTIONAL);
  } 
  public HDrawable transformsChildren(boolean b) { 
    _flags = HMath.setBits(_flags, BITMASK_TRANSFORMS_CHILDREN, b); 
    return this;
  } 
  public boolean transformsChildren() { 
    return HMath.hasBits(_flags, BITMASK_TRANSFORMS_CHILDREN);
  } 
  protected void onResize(float oldW, float oldH, float newW, float newH) { 
    if (proportional()) { 
      if (newH != oldH) { 
        if (oldH != 0) _width = oldW * newH/oldH;
      } 
      else if (newW != oldW) { 
        if (oldW != 0) _height = oldH * newW/oldW;
      }
    } 
    if (transformsChildren()) { 
      float scalew = (oldW==0)? 1 : _width/oldW; 
      float scaleh = (oldH==0)? 1 : _height/oldH; 
      HDrawable child = _firstChild; 
      while (child != null) { 
        child.loc(child._x*scalew, child._y*scaleh); 
        child.scale(scalew, scaleh); 
        child = child._next;
      }
    }
  } 
  public void bounds(PVector boundsLoc, PVector boundsSize) { 
    float[] vals = new float[4]; 
    bounds(vals); 
    boundsLoc.x = vals[0]; 
    boundsLoc.y = vals[1]; 
    boundsSize.x = vals[2]; 
    boundsSize.y = vals[3];
  } 
  public void bounds(float[] boundsValues) { 
    float x1 = -anchorX(), y1 = -anchorY(); 
    float x2 = x1+_width, y2 = y1+_height; 
    float minx, miny, maxx, maxy; 
    float[] tl = HMath.absLocArr(this, x1, y1); 
    minx = maxx = tl[0]; 
    miny = maxy = tl[1]; 
    float[] tr = HMath.absLocArr(this, x2, y1); 
    if (tr[0]<minx) minx=tr[0]; 
    else if (tr[0]>maxx) maxx=tr[0]; 
    if (tr[1]<miny) miny=tr[1]; 
    else if (tr[1]>maxy) maxy=tr[1]; 
    float[] bl = HMath.absLocArr(this, x1, y2); 
    if (bl[0]<minx) minx=bl[0]; 
    else if (bl[0]>maxx) maxx=bl[0]; 
    if (bl[1]<miny) miny=bl[1]; 
    else if (bl[1]>maxy) maxy=bl[1]; 
    float[] br = HMath.absLocArr(this, x2, y2); 
    if (br[0]<minx) minx=br[0]; 
    else if (br[0]>maxx) maxx=br[0]; 
    if (br[1]<miny) miny=br[1]; 
    else if (br[1]>maxy) maxy=br[1]; 
    boundsValues[0] = minx; 
    boundsValues[1] = miny; 
    boundsValues[2] = maxx - minx; 
    boundsValues[3] = maxy - miny;
  } 
  public PVector boundingSize() { 
    float cosVal = (float)Math.cos(_rotationZRad); 
    float sinVal = (float)Math.sin(_rotationZRad); 
    float drawX = -anchorX(); 
    float drawY = -anchorY(); 
    float x1 = drawX; 
    float x2 = _width + drawX; 
    float y1 = drawY; 
    float y2 = _height + drawY; 
    float[] xCoords = new float[4]; 
    float[] yCoords = new float[4]; 
    xCoords[0] = x1*cosVal + y1*sinVal; 
    yCoords[0] = x1*sinVal + y1*cosVal; 
    xCoords[1] = x2*cosVal + y1*sinVal; 
    yCoords[1] = x2*sinVal + y1*cosVal; 
    xCoords[2] = x1*cosVal + y2*sinVal; 
    yCoords[2] = x1*sinVal + y2*cosVal; 
    xCoords[3] = x2*cosVal + y2*sinVal; 
    yCoords[3] = x2*sinVal + y2*cosVal; 
    float minX = xCoords[3]; 
    float maxX = minX; 
    float minY = yCoords[3]; 
    float maxY = maxX; 
    for (int i=0; i<3; ++i) { 
      float x = xCoords[i]; 
      float y = yCoords[i]; 
      if (x < minX) minX = x; 
      else if (x > maxX) maxX = x; 
      if (y < minY) minY = y; 
      else if (y > maxY) maxY = y;
    } 
    return new PVector(maxX-minX, maxY-minY);
  } 
  public HDrawable fill(int clr) { 
    if (0 <= clr && clr <= 255) clr |= clr<<8 | clr<<16 | 0xFF000000; 
    _fill = clr; 
    onStyleChange(); 
    return this;
  } 
  public HDrawable fill(int clr, int alpha) { 
    if (0 <= clr && clr <= 255) clr |= clr<<8 | clr<<16; 
    _fill = HColors.setAlpha(clr, alpha); 
    onStyleChange(); 
    return this;
  } 
  public HDrawable fill(int r, int g, int b) { 
    _fill = HColors.merge(255, r, g, b); 
    onStyleChange(); 
    return this;
  } 
  public HDrawable fill(int r, int g, int b, int a) { 
    _fill = HColors.merge(a, r, g, b); 
    onStyleChange(); 
    return this;
  } 
  public int fill() { 
    return _fill;
  } 
  public HDrawable noFill() { 
    return fill(HConstants.CLEAR);
  } 
  public HDrawable stroke(int clr) { 
    if (0 <= clr && clr <= 255) clr |= clr<<8 | clr<<16 | 0xFF000000; 
    _stroke = clr; 
    onStyleChange(); 
    return this;
  } 
  public HDrawable stroke(int clr, int alpha) { 
    if (0 <= clr && clr <= 255) clr |= clr<<8 | clr<<16; 
    _stroke = HColors.setAlpha(clr, alpha); 
    onStyleChange(); 
    return this;
  } 
  public HDrawable stroke(int r, int g, int b) { 
    _stroke = HColors.merge(255, r, g, b); 
    onStyleChange(); 
    return this;
  } 
  public HDrawable stroke(int r, int g, int b, int a) { 
    _stroke = HColors.merge(a, r, g, b); 
    onStyleChange(); 
    return this;
  } 
  public int stroke() { 
    return _stroke;
  } 
  public HDrawable noStroke() { 
    return stroke(HConstants.CLEAR);
  } 
  public HDrawable strokeCap(int type) { 
    _strokeCap = type; 
    onStyleChange(); 
    return this;
  } 
  public int strokeCap() { 
    return _strokeCap;
  } 
  public HDrawable strokeJoin(int type) { 
    _strokeJoin = type; 
    onStyleChange(); 
    return this;
  } 
  public int strokeJoin() { 
    return _strokeJoin;
  } 
  public HDrawable strokeWeight(float f) { 
    _strokeWeight = f; 
    onStyleChange(); 
    return this;
  } 
  public float strokeWeight() { 
    return _strokeWeight;
  } 
  public HDrawable stylesChildren(boolean b) { 
    _flags = HMath.setBits(_flags, BITMASK_STYLES_CHILDREN, b); 
    return this;
  } 
  public boolean stylesChildren() { 
    return HMath.hasBits(_flags, BITMASK_STYLES_CHILDREN);
  } 
  protected void onStyleChange() { 
    if (stylesChildren()) { 
      HDrawable d = _firstChild; 
      while (d!=null) { 
        d._stroke = _stroke; 
        d._strokeWeight = _strokeWeight; 
        d._strokeJoin = _strokeJoin; 
        d._strokeCap = _strokeCap; 
        d._fill = _fill; 
        d = d._next;
      }
    }
  } 
  public HDrawable rotation(float deg) { 
    return rotationZ(deg);
  } 
  public float rotation() { 
    return rotationZ();
  } 
  public HDrawable rotationRad(float rad) { 
    return rotationZRad(rad);
  } 
  public float rotationRad() { 
    return rotationZRad();
  } 
  public HDrawable rotate(float deg) { 
    return rotateZ(deg);
  } 
  public HDrawable rotateRad(float rad) { 
    return rotateZRad(rad);
  } 
  public HDrawable rotationX(float deg) { 
    return rotationXRad(deg * HConstants.D2R);
  } 
  public float rotationX() { 
    return rotationXRad() * HConstants.R2D;
  } 
  public HDrawable rotationXRad(float rad) { 
    if (rotatesChildren()) { 
      for (HDrawable d=_firstChild;d!=null;) d=d.rotationXRad(rad).next();
    } 
    else _rotationXRad = rad; 
    return this;
  } 
  public float rotationXRad() { 
    return (rotatesChildren() && _firstChild!=null)? _firstChild.rotationXRad() : _rotationXRad;
  } 
  public HDrawable rotateX(float deg) { 
    return rotationXRad(_rotationXRad + deg*HConstants.D2R);
  } 
  public HDrawable rotateXRad(float rad) { 
    return rotationXRad(_rotationXRad + rad);
  } 
  public HDrawable rotationY(float deg) { 
    return rotationYRad(deg * HConstants.D2R);
  } 
  public float rotationY() { 
    return rotationYRad() * HConstants.R2D;
  } 
  public HDrawable rotationYRad(float rad) { 
    if (rotatesChildren()) { 
      for (HDrawable d=_firstChild;d!=null;) d=d.rotationYRad(rad).next();
    } 
    else _rotationYRad = rad; 
    return this;
  } 
  public float rotationYRad() { 
    return (rotatesChildren() && _firstChild!=null)? _firstChild.rotationYRad() : _rotationYRad;
  } 
  public HDrawable rotateY(float deg) { 
    return rotationYRad(_rotationYRad + deg*HConstants.D2R);
  } 
  public HDrawable rotateYRad(float rad) { 
    return rotationYRad(_rotationYRad + rad);
  } 
  public HDrawable rotationZ(float deg) { 
    return rotationZRad(deg * HConstants.D2R);
  } 
  public float rotationZ() { 
    return rotationZRad() * HConstants.R2D;
  } 
  public HDrawable rotationZRad(float rad) { 
    if (rotatesChildren()) { 
      for (HDrawable d=_firstChild;d!=null;) d=d.rotationZRad(rad).next();
    } 
    else _rotationZRad = rad; 
    return this;
  } 
  public float rotationZRad() { 
    return (rotatesChildren() && _firstChild!=null)? _firstChild.rotationZRad() : _rotationZRad;
  } 
  public HDrawable rotateZ(float deg) { 
    return rotationZRad(_rotationZRad + deg*HConstants.D2R);
  } 
  public HDrawable rotateZRad(float rad) { 
    return rotationZRad(_rotationZRad + rad);
  } 
  public HDrawable rotatesChildren(boolean b) { 
    _flags = HMath.setBits(_flags, BITMASK_ROTATES_CHILDREN, b); 
    return this;
  } 
  public boolean rotatesChildren() { 
    return HMath.hasBits(_flags, BITMASK_ROTATES_CHILDREN);
  } 
  public HDrawable alpha(int a) { 
    return alphaPc(a/255f);
  } 
  public int alpha() { 
    return Math.round( alphaPc()*255 );
  } 
  public HDrawable alphaPc(float f) { 
    _alphaPc = (f<0)? 0 : (f>1)? 1 : f; 
    return this;
  } 
  public float alphaPc() { 
    return (_alphaPc<0)? 0 : _alphaPc;
  } 
  public HDrawable visibility(boolean v) { 
    if ( v && (_alphaPc==0) ) { 
      _alphaPc = 1;
    } 
    else if ( v == (_alphaPc<0) ) { 
      _alphaPc = -_alphaPc;
    } 
    return this;
  } 
  public boolean visibility() { 
    return _alphaPc > 0;
  } 
  public HDrawable show() { 
    return visibility(true);
  } 
  public HDrawable hide() { 
    return visibility(false);
  } 
  public HDrawable alphaShift(int da) { 
    return alphaShiftPc( da/255f );
  } 
  public HDrawable alphaShiftPc(float f) { 
    return alphaPc(_alphaPc + f);
  } 
  public float x2u(float px) { 
    return px / (_width==0? 100 : _width);
  } 
  public float y2v(float px) { 
    return px / (_height==0? 100 : _height);
  } 
  public float u2x(float pc) { 
    return pc * _width;
  } 
  public float v2y(float pc) { 
    return pc * _height;
  } 
  public HDrawable extras(HBundle b) { 
    _extras = b; 
    return this;
  } 
  public HBundle extras() { 
    return _extras;
  } 
  public HDrawable obj(String key, Object value) { 
    if (_extras == null) _extras = new HBundle(); 
    _extras.obj(key, value); 
    return this;
  } 
  public HDrawable num(String key, float value) { 
    if (_extras == null) _extras = new HBundle(); 
    _extras.num(key, value); 
    return this;
  } 
  public HDrawable bool(String key, boolean value) { 
    if (_extras == null) _extras = new HBundle(); 
    _extras.bool(key, value); 
    return this;
  } 
  public Object obj(String key) { 
    return (_extras==null)? null : _extras.obj(key);
  } 
  public String str(String key) { 
    return (_extras==null)? null : _extras.str(key);
  } 
  public float num(String key) { 
    return (_extras==null)? 0 : _extras.num(key);
  } 
  public int numI(String key) { 
    return (_extras==null)? 0 : _extras.numI(key);
  } 
  public boolean bool(String key) { 
    return (_extras==null)? false : _extras.bool(key);
  } 
  public boolean contains(float absX, float absY, float absZ) { 
    PApplet app = H.app(); 
    absZ -= _z; 
    return contains( app.screenX(absX, absY, absZ), app.screenY(absX, absY, absZ));
  } 
  public boolean contains(float absX, float absY) { 
    float[] rel = HMath.relLocArr(this, absX, absY); 
    rel[0] += anchorX(); 
    rel[1] += anchorY(); 
    return containsRel(rel[0], rel[1]);
  } 
  public boolean containsRel(float relX, float relY, float relZ) { 
    PApplet app = H.app(); 
    relZ -= _z; 
    return containsRel( app.screenX(relX, relY, relZ), app.screenY(relX, relY, relZ));
  } 
  public boolean containsRel(float relX, float relY) { 
    return (0 <= relX) && (relX <= _width) && (0 <= relY) && (relY <= _height);
  } 
  protected void applyStyle(PGraphics g, float currAlphaPc) { 
    float faPc = currAlphaPc * (_fill >>> 24); 
    g.fill(_fill | 0xFF000000, Math.round(faPc)); 
    if (_strokeWeight > 0) { 
      float saPc = currAlphaPc * (_stroke >>> 24); 
      g.stroke(_stroke | 0xFF000000, Math.round(saPc)); 
      g.strokeWeight(_strokeWeight); 
      g.strokeCap(_strokeCap); 
      g.strokeJoin(_strokeJoin);
    } 
    else g.noStroke();
  } 
  public void paintAll(PGraphics g, boolean usesZ, float currAlphaPc) { 
    if (_alphaPc<=0) return; 
    g.pushMatrix(); 
    if (usesZ) { 
      g.translate(_x, _y, _z); 
      g.rotateX(_rotationXRad); 
      g.rotateY(_rotationYRad); 
      g.rotateZ(_rotationZRad);
    } 
    else { 
      g.translate(_x, _y); 
      g.rotate(_rotationZRad);
    } 
    currAlphaPc *= _alphaPc; 
    draw(g, usesZ, -anchorX(), -anchorY(), currAlphaPc); 
    HDrawable child = _firstChild; 
    while (child != null) { 
      child.paintAll(g, usesZ, currAlphaPc); 
      child = child._next;
    } 
    g.popMatrix();
  } 
  public abstract void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float currAlphaPc); 
  public static class HDrawableIterator implements Iterator<HDrawable> { 
    private HDrawable parent, d1, d2; 
    public HDrawableIterator(HDrawable parentDrawable) { 
      parent = parentDrawable; 
      d1 = parent._firstChild; 
      if (d1 != null) d2 = d1._next;
    } 
    public boolean hasNext() { 
      return (d1 != null);
    } 
    public HDrawable next() { 
      HDrawable nxt = d1; 
      d1 = d2; 
      if (d2 != null) d2 = d2._next; 
      return nxt;
    } 
    public void remove() { 
      if (d1 != null) d1.popOut();
    }
  }
} 
public static abstract class HDrawable3D extends HDrawable { 
  public static final float DEFAULT_DEPTH = 100; 
  protected float _depth; 
  protected float _anchorW; 
  public HDrawable3D() { 
    _depth = DEFAULT_DEPTH;
  } 
  public HDrawable3D size(float s) { 
    return size(s, s, s);
  } 
  public HDrawable3D size(float w, float h, float d) { 
    _width = w; 
    _height = h; 
    _depth = d; 
    return this;
  } 
  public PVector size() { 
    return new PVector(_width, _height, _depth);
  } 
  public HDrawable3D depth(float f) { 
    _depth = f; 
    return this;
  } 
  public float depth() { 
    return _depth;
  } 
  public HDrawable3D scale(float s) { 
    return scale(s, s, s);
  } 
  public HDrawable3D scale(float sw, float sh, float sd) { 
    return (HDrawable3D) depth(sd).scale(sw, sh);
  } 
  public HDrawable3D anchor(float ancx, float ancy, float ancz) { 
    return (HDrawable3D) anchorZ(ancz).anchorX(ancx).anchorY(ancy);
  } 
  public HDrawable3D anchorAt(int where) { 
    if ( (where & HConstants.CENTER_X) != 0 ) { 
      anchorU(0.5f);
    } 
    else if ( (where & HConstants.LEFT) != 0 ) { 
      anchorU(0);
    } 
    else if ( (where & HConstants.RIGHT) != 0 ) { 
      anchorU(1);
    } 
    if ( (where & HConstants.CENTER_Y) != 0 ) { 
      anchorV(0.5f);
    } 
    else if ( (where & HConstants.TOP) != 0 ) { 
      anchorV(0);
    } 
    else if ( (where & HConstants.BOTTOM) != 0 ) { 
      anchorV(1);
    } 
    if ( (where & HConstants.CENTER_Z) != 0 ) { 
      anchorW(0.5f);
    } 
    else if ( (where & HConstants.BACK) != 0 ) { 
      anchorW(0);
    } 
    else if ( (where & HConstants.FRONT) != 0 ) { 
      anchorW(1);
    } 
    return this;
  } 
  protected void onResize(float oldW, float oldH, float newW, float newH) { 
    super.onResize(oldW, oldH, newW, newH);
  } 
  public PVector anchor() { 
    return new PVector(anchorX(), anchorY(), anchorZ());
  } 
  public PVector anchorUV() { 
    return new PVector(_anchorU, _anchorV, _anchorW);
  } 
  public HDrawable3D anchorZ(float f) { 
    return anchorZ(z2w(f));
  } 
  public float anchorZ() { 
    return w2z(_anchorW);
  } 
  public HDrawable3D anchorW(float f) { 
    _anchorW = f; 
    return this;
  } 
  public float anchorW() { 
    return _anchorW;
  } 
  public float z2w(float px) { 
    return px / (_depth==0? DEFAULT_DEPTH : _depth);
  } 
  public float w2z(float pc) { 
    return pc * _depth;
  }
} 


public static class HStage extends HDrawable implements HImageHolder { 
  private PApplet _app; 
  private PImage _bgImg; 
  private boolean _autoClears; 
  private boolean _showsFPS; 
  public HStage(PApplet papplet) { 
    _app = papplet; 
    _autoClears = true; 
    background(HConstants.DEFAULT_BACKGROUND_COLOR);
  } 
  public boolean invalidChild(HDrawable destParent) { 
    return true;
  } 
  public HStage background(int clr) { 
    _fill = clr; 
    return clear();
  } 
  public HStage backgroundImg(Object arg) { 
    return image(arg);
  } 
  public HStage image(Object imgArg) { 
    _bgImg = H.getImage(imgArg); 
    return clear();
  } 
  public PImage image() { 
    return _bgImg;
  } 
  public HStage autoClear(boolean b) { 
    _autoClears = b; 
    return this;
  } 
  public HStage autoClears(boolean b) { 
    _autoClears = b; 
    return this;
  } 
  public boolean autoClears() { 
    return _autoClears;
  } 
  public HStage clear() { 
    if (_bgImg == null) _app.background(_fill); 
    else _app.background(_bgImg); 
    return this;
  } 
  public HDrawable fill(int clr) { 
    background(clr); 
    return this;
  } 
  public HDrawable fill(int clr, int alpha) { 
    return fill(clr);
  } 
  public HDrawable fill(int r, int g, int b) { 
    return fill(HColors.merge(255, r, g, b));
  } 
  public HDrawable fill(int r, int g, int b, int a) { 
    return fill(r, g, b);
  } 
  public PVector size() { 
    return new PVector(_app.width, _app.height);
  } 
  public float width() { 
    return _app.width;
  } 
  public float height() { 
    return _app.height;
  } 
  public HStage showsFPS(boolean b) { 
    _showsFPS = b; 
    return this;
  } 
  public boolean showsFPS() { 
    return _showsFPS;
  } 
  public void paintAll(PGraphics g, boolean usesZ, float currAlphaPc) { 
    g.pushStyle(); 
    if (_autoClears) clear(); 
    HDrawable child = _firstChild; 
    while (child != null) { 
      child.paintAll(g, usesZ, currAlphaPc); 
      child = child.next();
    } 
    g.popStyle(); 
    if (_showsFPS) { 
      g.pushStyle(); 
      g.fill(H.BLACK); 
      g.text(_app.frameRate, 1, 17); 
      g.fill(H.WHITE); 
      g.text(_app.frameRate, 0, 16); 
      g.popStyle();
    }
  } 
  public void draw(PGraphics g, boolean b, float x, float y, float p) {
  } 
  public HDrawable createCopy() { 
    return null;
  }
} 
public static interface HCallback { 
  public void run(Object obj);
} 
public static interface HDirectable extends HLocatable, HRotatable {
} 
public static interface HHittable { 
  public boolean contains(float absX, float absY, float absZ); 
  public boolean contains(float absX, float absY); 
  public boolean containsRel(float relX, float relY, float relZ); 
  public boolean containsRel(float relX, float relY);
} 
public static interface HImageHolder { 
  public HImageHolder image(Object imgArg); 
  public PImage image();
} 
public static interface HLocatable { 
  public float x(); 
  public HLocatable x(float f); 
  public float y(); 
  public HLocatable y(float f); 
  public float z(); 
  public HLocatable z(float f);
} 
public static interface HRotatable { 
  public float rotationRad(); 
  public HRotatable rotationRad(float rad);
} 
public static interface HLayout { 
  public void applyTo(HDrawable target); 
  public abstract PVector getNextPoint();
} 


public static class H implements HConstants { 
  private static H _self; 
  private static PApplet _app; 
  private static PGraphics _graphicsContext; 
  private static HStage _stage; 
  private static HBehaviorRegistry _behaviors; 
  private static HMouse _mouse; 
  private static boolean _uses3D; 
  public static H init(PApplet applet) { 
    _app = applet; 
    if (_self == null) _self = new H(); 
    if (_stage == null) _stage = new HStage(_app); 
    if (_behaviors == null) _behaviors = new HBehaviorRegistry(); 
    if (_mouse == null) _mouse = new HMouse(_app); 
    try { 
      int dummyVar = _app.g.A; 
      _graphicsContext = _app.g;
    } 
    catch(Exception e) { 
      Object o = _app; 
      _graphicsContext = (PGraphics) o;
    } 
    return _self;
  } 
  public static HStage stage() { 
    return _stage;
  } 
  public static PApplet app() { 
    return _app;
  } 
  public static HBehaviorRegistry behaviors() { 
    return _behaviors;
  } 
  public static HMouse mouse() { 
    return _mouse;
  } 
  public static H use3D(boolean b) { 
    _uses3D = b; 
    return _self;
  } 
  public static boolean uses3D() { 
    return _uses3D;
  } 
  public static H background(int clr) { 
    _stage.background(clr); 
    return _self;
  } 
  public static H backgroundImg(Object arg) { 
    _stage.backgroundImg(arg); 
    return _self;
  } 
  public static H autoClear(boolean b) { 
    _stage.autoClear(b); 
    return _self;
  } 
  public static H autoClears(boolean b) { 
    _stage.autoClears(b); 
    return _self;
  } 
  public static boolean autoClears() { 
    return _stage.autoClears();
  } 
  public static H clearStage() { 
    _stage.clear(); 
    return _self;
  } 
  public static HCanvas add(HCanvas stageChild) { 
    return (HCanvas) _stage.add(stageChild);
  } 
  public static HEllipse add(HEllipse stageChild) { 
    return (HEllipse) _stage.add(stageChild);
  } 
  public static HGroup add(HGroup stageChild) { 
    return (HGroup) _stage.add(stageChild);
  } 
  public static HImage add(HImage stageChild) { 
    return (HImage) _stage.add(stageChild);
  } 
  public static HPath add(HPath stageChild) { 
    return (HPath) _stage.add(stageChild);
  } 
  public static HRect add(HRect stageChild) { 
    return (HRect) _stage.add(stageChild);
  } 
  public static HShape add(HShape stageChild) { 
    return (HShape) _stage.add(stageChild);
  } 
  public static HText add(HText stageChild) { 
    return (HText) _stage.add(stageChild);
  } 
  public static HDrawable add(HDrawable stageChild) { 
    return _stage.add(stageChild);
  } 
  public static HCanvas remove(HCanvas stageChild) { 
    return (HCanvas) _stage.remove(stageChild);
  } 
  public static HEllipse remove(HEllipse stageChild) { 
    return (HEllipse) _stage.remove(stageChild);
  } 
  public static HGroup remove(HGroup stageChild) { 
    return (HGroup) _stage.remove(stageChild);
  } 
  public static HImage remove(HImage stageChild) { 
    return (HImage) _stage.remove(stageChild);
  } 
  public static HPath remove(HPath stageChild) { 
    return (HPath) _stage.remove(stageChild);
  } 
  public static HRect remove(HRect stageChild) { 
    return (HRect) _stage.remove(stageChild);
  } 
  public static HShape remove(HShape stageChild) { 
    return (HShape) _stage.remove(stageChild);
  } 
  public static HText remove(HText stageChild) { 
    return (HText) _stage.remove(stageChild);
  } 
  public static HDrawable remove(HDrawable stageChild) { 
    return _stage.remove(stageChild);
  } 
  public static H drawStage() { 
    _behaviors.runAll(_app); 
    _mouse.handleEvents(); 
    _stage.paintAll(_graphicsContext, _uses3D, 1); 
    return _self;
  } 
  public static boolean mouseStarted() { 
    return _mouse.started();
  } 
  public static PImage getImage(Object imgArg) { 
    if (imgArg instanceof PImage) return (PImage) imgArg; 
    if (imgArg instanceof HImageHolder) return ((HImageHolder)imgArg).image(); 
    if (imgArg instanceof String) return _app.loadImage((String) imgArg); 
    return null;
  } 
  private H() {
  }
} 


public static class HBundle { 
  private HashMap<String, Object> objectContents; 
  private HashMap<String, Float> numberContents; 
  public HBundle() { 
    objectContents = new HashMap<String, Object>(); 
    numberContents = new HashMap<String, Float>();
  } 
  public HBundle obj(String key, Object value) { 
    objectContents.put(key, value); 
    return this;
  } 
  public HBundle num(String key, float value) { 
    numberContents.put(key, value); 
    return this;
  } 
  public HBundle bool(String key, boolean value) { 
    numberContents.put(key, (value? 1f : 0f) ); 
    return this;
  } 
  public Object obj(String key) { 
    return objectContents.get(key);
  } 
  public String str(String key) { 
    Object o = objectContents.get(key); 
    if (o instanceof String) return (String) o; 
    return null;
  } 
  public float num(String key) { 
    return numberContents.get(key);
  } 
  public int numI(String key) { 
    return Math.round(numberContents.get(key));
  } 
  public boolean bool(String key) { 
    return (numberContents.get(key) != 0);
  }
} 


public static class HCapture { 
  private PGraphics _capturer; 
  private String _renderer; 
  private String _filename; 
  private boolean _isRecording; 
  private int _start, _end; 
  public HCapture() { 
    _start = _end = -1;
  } 
  public HCapture capture() { 
    _start = _end = H.app().frameCount; 
    return this;
  } 
  public HCapture capture(int frameNum) { 
    _start = _end = H.app().frameCount; 
    return this;
  } 
  public HCapture start(int frameNum) { 
    _start = frameNum; 
    return this;
  } 
  public int start() { 
    return _start;
  } 
  public HCapture end(int frameNum) { 
    _end = frameNum; 
    return this;
  } 
  public int end() { 
    return _end;
  } 
  public boolean isRecording() { 
    return _isRecording;
  } 
  public HCapture filename(String s) { 
    _filename = s; 
    return this;
  } 
  public String filename() { 
    return _filename;
  } 
  public HCapture renderer(String s) { 
    _renderer = s; 
    return this;
  } 
  public String renderer() { 
    return _renderer;
  } 
  public void run() { 
    if (_isRecording) { 
      if (_end < 0) { 
        if (H.app().frameCount >= _end) _isRecording = false;
      } 
      else { 
        PApplet app = H.app();
      } 
      if (!_isRecording) {
      }
    } 
    else { 
      if (_start < 0) {
      } 
      else {
      } 
      if (_isRecording) { 
        H.app().endRecord(); 
        if (_capturer != null) { 
          _capturer.save(_filename); 
          _capturer = null;
        }
      }
    }
  }
} 


public static class HColors implements HConstants { 
  public static int[] explode(int clr) { 
    int[] explodedColors = new int[4]; 
    for (int i=0; i<4; ++i) explodedColors[3-i] = (clr >>> (i*8)) & 0xFF; 
    return explodedColors;
  } 
  public static int merge(int a, int r, int g, int b) { 
    if (a < 0) a = 0; 
    else if (a > 255) a = 255; 
    if (r < 0) r = 0; 
    else if (r > 255) r = 255; 
    if (g < 0) g = 0; 
    else if (g > 255) g = 255; 
    if (b < 0) b = 0; 
    else if (b > 255) b = 255; 
    return (a<<24) | (r<<16) | (g<<8) | b;
  } 
  public static int setAlpha(int clr, int newClr) { 
    if (newClr < 0) newClr = 0; 
    else if (newClr > 255) newClr = 255; 
    return clr & 0x00FFFFFF | (newClr << 24);
  } 
  public static int setRed(int clr, int newClr) { 
    if (newClr < 0) newClr = 0; 
    else if (newClr > 255) newClr = 255; 
    return clr & 0xFF00FFFF | (newClr << 16);
  } 
  public static int setGreen(int clr, int newClr) { 
    if (newClr < 0) newClr = 0; 
    else if (newClr > 255) newClr = 255; 
    return clr & 0xFFFF00FF | (newClr << 8);
  } 
  public static int setBlue(int clr, int newClr) { 
    if (newClr < 0) newClr = 0; 
    else if (newClr > 255) newClr = 255; 
    return clr & 0xFFFFFF00 | newClr;
  } 
  public static int getAlpha(int clr) { 
    return clr >>> 24;
  } 
  public static int getRed(int clr) { 
    return (clr >>> 16) & 255;
  } 
  public static int getGreen(int clr) { 
    return (clr >>> 8) & 255;
  } 
  public static int getBlue(int clr) { 
    return clr & 255;
  } 
  public static boolean isTransparent(int clr) { 
    return (clr & 0xFF000000) == 0;
  }
} 
public static interface HConstants { 
  public static final int NONE = 0, LEFT = 1, RIGHT = 2, CENTER_X = 3, TOP = 4, BOTTOM = 8, CENTER_Y = 12, BACK = 16, FRONT = 32, CENTER_Z = 48, CENTER = 63, TOP_LEFT = 5, TOP_RIGHT = 6, BOTTOM_LEFT = 9, BOTTOM_RIGHT = 10, CENTER_LEFT = 13, CENTER_RIGHT = 14, CENTER_TOP = 7, CENTER_BOTTOM = 11, DEFAULT_BACKGROUND_COLOR = 0xFFECF2F5, CLEAR = 0x00FFFFFF, WHITE = 0xFFFFFFFF, LGREY = 0xFFC0C0C0, GREY = 0xFF808080, DGREY = 0xFF404040, BLACK = 0xFF000000, RED = 0xFFFF0000, GREEN = 0xFF00FF00, BLUE = 0xFF0000FF, CYAN = 0xFF00FFFF, MAGENTA = 0xFFFF00FF, YELLOW = 0xFFFFFF00, SAW = 0, SINE = 1, TRIANGLE = 2, SQUARE = 3, WIDTH = 0, HEIGHT = 1, SIZE = 2, ALPHA = 3, X = 4, Y = 5, Z = 6, LOCATION = 7, ROTATION = 8, DROTATION = 9, DX = 10, DY = 11, DZ = 12, DLOC = 13, SCALE = 14, ROTATIONX = 15, ROTATIONY = 16, ROTATIONZ = 8, DROTATIONX = 17, DROTATIONY = 18, DROTATIONZ = 9, ISOCELES = 0, EQUILATERAL = 1, ONES = 0xFFFFFFFF, ZEROES = 0; 
  public static final float D2R = PConstants.PI / 180f, R2D = 180f / PConstants.PI, SQRT2 = 1.4142135623730951f, PHI = 1.618033988749895f, PHI_1 = 0.618033988749895f, TOLERANCE = (float)10e-6, EPSILON = (float)10e-12; 
  public static final HCallback NOP = new HCallback() {
    public void run(Object obj) {
    }
  };
} 


public static class HMath implements HConstants { 
  private static boolean _usingTempSeed; 
  private static int _resetSeedValue; 
  public static float dist(float x1, float y1, float x2, float y2) { 
    float w = x2 - x1; 
    float h = y2 - y1; 
    return (float) Math.sqrt(w*w + h*h);
  } 
  public static float[] rotatePointArr(float x, float y, float rad) { 
    float[] pt = new float[2]; 
    float c = (float) Math.cos(rad); 
    float s = (float) Math.sin(rad); 
    pt[0] = x*c - y*s; 
    pt[1] = x*s + y*c; 
    return pt;
  } 
  public static PVector rotatePoint(float x, float y, float rad) { 
    float[] f = rotatePointArr(x, y, rad); 
    return new PVector(f[0], f[1]);
  } 
  public static float yAxisAngle(float x1, float y1, float x2, float y2) { 
    return (float) Math.atan2(x2-x1, y2-y1);
  } 
  public static float xAxisAngle(float x1, float y1, float x2, float y2) { 
    return (float) Math.atan2(y2-y1, x2-x1);
  } 
  public static float[] absLocArr(HDrawable ref, float relX, float relY) { 
    float[] f = {
      relX, relY, 0
    }; 
    while (ref != null) { 
      float rot = ref.rotationRad(); 
      float[] g = rotatePointArr(f[0], f[1], rot); 
      f[0] = g[0] + ref.x(); 
      f[1] = g[1] + ref.y(); 
      f[2] += rot; 
      ref = ref.parent();
    } 
    return f;
  } 
  public static PVector absLoc(HDrawable ref, float relX, float relY) { 
    float[] f = absLocArr(ref, relX, relY); 
    return new PVector(f[0], f[1]);
  } 
  public static PVector absLoc(HDrawable d) { 
    return absLoc(d, 0, 0);
  } 
  public static float[] relLocArr(HDrawable ref, float absX, float absY) { 
    float[] f = absLocArr(ref, 0, 0); 
    return rotatePointArr(absX-f[0], absY-f[1], -f[2]);
  } 
  public static PVector relLoc(HDrawable ref, float absX, float absY) { 
    float[] f = relLocArr(ref, absX, absY); 
    return new PVector(f[0], f[1]);
  } 
  public static int quadrant(float cx, float cy, float x, float y) { 
    return (y>=cy)? (x>=cx? 1 : 2) : 
    (x>=cx? 4 : 3);
  } 
  public static int quadrant(float dcx, float dcy) { 
    return (dcy>=0)? (dcx>=0? 1 : 2) : 
    (dcx>=0? 4 : 3);
  } 
  public static float ellipseRadius(float a, float b, float deg) { 
    return ellipseRadiusRad(a, b, deg * D2R);
  } 
  public static float ellipseRadiusRad(float a, float b, float rad) { 
    float cosb = b * (float)Math.cos(rad); 
    float sina = a * (float)Math.sin(rad); 
    return a*b / (float)Math.sqrt(cosb*cosb + sina*sina);
  } 
  public static PVector ellipsePoint( float cx, float cy, float a, float b, float deg ) { 
    return ellipsePointRad(cx, cy, a, b, deg*D2R);
  } 
  public static PVector ellipsePointRad( float cx, float cy, float a, float b, float rad ) { 
    float[] f = ellipsePointRadArr(cx, cy, a, b, rad); 
    return new PVector(f[0], f[1]);
  } 
  public static float[] ellipsePointRadArr( float cx, float cy, float a, float b, float rad ) { 
    float[] f = new float[3]; 
    f[2] = ellipseRadiusRad(a, b, rad); 
    f[0] = f[2] * (float)Math.cos(rad) + cx; 
    f[1] = f[2] * (float)Math.sin(rad) + cy; 
    return f;
  } 
  public static float normalizeAngle(float deg) { 
    return normalizeAngleRad(deg * D2R) * R2D;
  } 
  public static float normalizeAngleRad(float rad) { 
    rad %= PConstants.TWO_PI; 
    if (rad < -PConstants.PI) rad += PConstants.TWO_PI; 
    else if (rad > PConstants.PI) rad -= PConstants.TWO_PI; 
    return rad;
  } 
  public static float normalizeAngle2(float deg) { 
    return normalizeAngleRad2(deg * D2R) * R2D;
  } 
  public static float normalizeAngleRad2(float rad) { 
    float norm = rad % PConstants.TWO_PI; 
    if (norm < 0) norm += PConstants.TWO_PI; 
    return norm;
  } 
  public static float squishAngle(float w, float h, float deg) { 
    return squishAngle(w, h, deg * D2R) * R2D;
  } 
  public static float squishAngleRad(float w, float h, float rad) { 
    float dx = (float)Math.cos(rad) * w/h; 
    float dy = (float)Math.sin(rad); 
    return (float) Math.atan2(dy, dx);
  } 
  public static float lineSide( float x1, float y1, float x2, float y2, float ptx, float pty ) { 
    return (x2-x1)*(pty-y1) - (y2-y1)*(ptx-x1);
  } 
  public static boolean collinear( float x1, float y1, float x2, float y2, float ptx, float pty ) { 
    return (lineSide(x1, y1, x2, y2, ptx, pty) == 0);
  } 
  public static boolean leftOfLine( float x1, float y1, float x2, float y2, float ptx, float pty ) { 
    return (lineSide(x1, y1, x2, y2, ptx, pty) < 0);
  } 
  public static boolean rightOfLine( float x1, float y1, float x2, float y2, float ptx, float pty ) { 
    return (lineSide(x1, y1, x2, y2, ptx, pty) > 0);
  } 
  public static int solveCubic( float a, float b, float c, float d, float[] roots ) { 
    if (Math.abs(a) < EPSILON) return solveQuadratic(b, c, d, roots); 
    b /= a; 
    c /= a; 
    d /= a; 
    float bb = b*b; 
    float p = (bb - 3*c) / 9f; 
    float ppp = p*p*p; 
    float q = (2*bb*b - 9*b*c + 27*d) / 54; 
    float D = q*q - ppp; 
    b /= 3f; 
    if (Math.abs(D) < EPSILON) { 
      if (Math.abs(q) < EPSILON) { 
        roots[0] = -b; 
        return 1;
      } 
      float sqrtp = (float)Math.sqrt(p); 
      float signq = (q>0)? 1 : -1; 
      roots[0] = -signq*2*sqrtp - b; 
      roots[1] = signq*sqrtp - b; 
      return 2;
    } 
    if (D < 0) { 
      float sqrtp = (float)Math.sqrt(p); 
      float phi = (float)Math.acos(q / (sqrtp*sqrtp*sqrtp)) / 3; 
      float t = -2*sqrtp; 
      float o = PConstants.TWO_PI/3f; 
      roots[0] = t*(float)Math.cos(phi) - b; 
      roots[1] = t*(float)Math.cos(phi + o) - b; 
      roots[2] = t*(float)Math.cos(phi - o) - b; 
      return 3;
    } 
    float A = (q>0?-1:1) * (float)Math.pow(Math.abs(q) + Math.sqrt(D), 1.0/3.0); 
    roots[0] = A + p/A - b; 
    return 1;
  } 
  public static int solveQuadratic(float a, float b, float c, float[] roots) { 
    if (Math.abs(a) < EPSILON) { 
      if (Math.abs(b) >= EPSILON) { 
        roots[0] = -c/b; 
        return 1;
      } 
      return (Math.abs(c)<EPSILON)? -1 : 0;
    } 
    float q = b*b - 4*a*c; 
    if (q < 0) return 0; 
    q = (float)Math.sqrt(q); 
    a *= 2; 
    int numRoots = 0; 
    roots[numRoots++] = (-b-q) / a; 
    if (q > 0) roots[numRoots++] = (-b+q) / a; 
    return numRoots;
  } 
  public static int bezierParam( float p0, float p1, float p2, float p3, float val, float[] params ) { 
    float max = p0; 
    if (max < p1) max = p1; 
    if (max < p2) max = p2; 
    if (max < p3) max = p3; 
    float min = p0; 
    if (min > p1) min = p1; 
    if (min > p2) min = p2; 
    if (min > p3) min = p3; 
    if (val<min || val>max) return 0; 
    float a = 3*(p1-p2) - p0 + p3; 
    float b = 3*(p0 - 2*p1 + p2); 
    float c = 3*(p1-p0); 
    float d = p0 - val; 
    return solveCubic(a, b, c, d, params);
  } 
  public static int bezierParam( float p0, float p1, float p2, float val, float[] params ) { 
    float max = p0; 
    if (max < p1) max = p1; 
    if (max < p2) max = p2; 
    float min = p0; 
    if (min > p1) min = p1; 
    if (min > p2) min = p2; 
    if (val<min || val>max) return 0; 
    float a = p2 - 2*p1 + p0; 
    float b = 2 * (p1-p0); 
    float c = p0 - val; 
    return solveQuadratic(a, b, c, params);
  } 
  public static float bezierPoint( float p0, float p1, float p2, float p3, float t ) { 
    float tt = t*t; 
    float a = 3*(p1-p2) - p0 + p3; 
    float b = 3*(p0 - 2*p1 + p2); 
    float c = 3*(p1-p0); 
    return a*tt*t + b*tt + c*t + p0;
  } 
  public static float bezierPoint(float p0, float p1, float p2, float t) { 
    float a = p2 - 2*p1 + p0; 
    float b = 2 * (p1-p0); 
    return a*t*t + b*t + p0;
  } 
  public static float bezierTangent( float p0, float p1, float p2, float p3, float t ) { 
    float a = 3 * (3*(p1-p2) - p0 + p3); 
    float b = 6 * (p0 - 2*p1 + p2); 
    float c = 3 * (p1-p0); 
    return a*t*t + b*t + c;
  } 
  public static float bezierTangent(float p0, float p1, float p2, float t) { 
    float a = 2 * (p2 - 2*p1 + p0); 
    float b = 2 * (p1-p0); 
    return a*t + b;
  } 
  public static int randomInt(float high) { 
    return (int) Math.floor( H.app().random(high) );
  } 
  public static int randomInt(float low, float high) { 
    return (int) Math.floor( H.app().random(low, high) );
  } 
  public static int randomInt32() { 
    return randomInt(-2147483648, 2147483647);
  } 
  public static void tempSeed(long seed) { 
    if (!_usingTempSeed) { 
      _resetSeedValue = randomInt32(); 
      _usingTempSeed = true;
    } 
    H.app().randomSeed(seed);
  } 
  public static void removeTempSeed() { 
    H.app().randomSeed(_resetSeedValue);
  } 
  public static float sineWave(float stepDegrees) { 
    return (float) Math.sin(stepDegrees * H.D2R);
  } 
  public static float triangleWave(float stepDegrees) { 
    float outVal = (stepDegrees % 180) / 90; 
    if (outVal > 1) outVal = 2-outVal; 
    if (stepDegrees % 360 > 180) outVal = -outVal; 
    return outVal;
  } 
  public static float sawWave(float stepDegrees) { 
    float outVal = (stepDegrees % 180) / 180; 
    if (stepDegrees % 360 >= 180) outVal -= 1; 
    return outVal;
  } 
  public static float squareWave(float stepDegrees) { 
    return (stepDegrees % 360 > 180)? -1 : 1;
  } 
  public static boolean hasBits(byte target, byte mask) { 
    return (target&mask) == mask;
  } 
  public static boolean hasBits(int target, int mask) { 
    return (target&mask) == mask;
  } 
  public static byte setBits(byte target, byte mask, boolean val) { 
    return (byte) (val? target|mask : target&(~mask));
  } 
  public static int setBits(int target, int mask, boolean val) { 
    return (val)? target|mask : target&(~mask);
  } 
  public static boolean lessThan(float a, float b, float tolerance) { 
    return a < b + tolerance;
  } 
  public static boolean lessThan(float a, float b) { 
    return a < b + TOLERANCE;
  } 
  public static boolean greaterThan(float a, float b, float tolerance) { 
    return b < a + tolerance;
  } 
  public static boolean greaterThan(float a, float b) { 
    return b < a + TOLERANCE;
  } 
  public static boolean isEqual(float a, float b, float tolerance) { 
    return Math.abs(a-b) < tolerance;
  } 
  public static boolean isEqual(float a, float b) { 
    return Math.abs(a-b) < TOLERANCE;
  } 
  public static boolean isZero(float a, float tolerance) { 
    return Math.abs(a) < tolerance;
  } 
  public static boolean isZero(float a) { 
    return Math.abs(a) < TOLERANCE;
  } 
  public static float map(float val, float start1, float stop1, float start2, float stop2 ) { 
    return start2 + (stop2-start2) * (val-start1)/(stop1-start1);
  } 
  public static float round512(float val) { 
    return Math.round(val*512)/512f;
  }
} 


public static class HMouse implements HLocatable { 
  private PApplet _app; 
  private int _button; 
  private boolean _started, _moved; 
  public HMouse(PApplet app) { 
    _app = app;
  } 
  public boolean started() { 
    return _started;
  } 
  public boolean moved() { 
    return _moved;
  } 
  public int button() { 
    return _button;
  } 
  public void handleEvents() { 
    _button = _app.mouseButton; 
    if (!_moved) _moved = (_app.pmouseX != 0) || (_app.pmouseY != 0); 
    else if (!_started) _started = true;
  } 
  public float x() { 
    return _app.mouseX;
  } 
  public HMouse x(float newX) { 
    return this;
  } 
  public float y() { 
    return _app.mouseY;
  } 
  public HMouse y(float newY) { 
    return this;
  } 
  public float z() { 
    return 0;
  } 
  public HMouse z(float newZ) { 
    return this;
  }
} 


public static class HVector implements HLocatable { 
  private float _x, _y, _z; 
  public HVector() {
  } 
  public HVector(float xCoord, float yCoord) { 
    _x = xCoord; 
    _y = yCoord;
  } 
  public HVector(float xCoord, float yCoord, float zCoord) { 
    _x = xCoord; 
    _y = yCoord; 
    _z = zCoord;
  } 
  public float x() { 
    return _x;
  } 
  public HVector x(float newX) { 
    _x = newX; 
    return this;
  } 
  public float y() { 
    return _y;
  } 
  public HVector y(float newY) { 
    _y = newY; 
    return this;
  } 
  public float z() { 
    return _z;
  } 
  public HVector z(float newZ) { 
    _z = newZ; 
    return this;
  }
} 


public static class HWarnings { 
  public static final String NULL_TARGET = "A target should be assigned before using this method.", NO_PROTOTYPE = "This pool needs at least one prototype before requesting.", NULL_ARGUMENT = "This method does not take null arguments.", INVALID_DEST = "The destination doesn't not belong to any parent.", DESTCEPTION = "The destination cannot be itself", CHILDCEPTION = "Can't add this parent as its own child.", INVALID_CHILD = "The child you're trying to add is cannot be added to this drawable." ; 
  public static void warn(String type, String loc, String msg) { 
    PApplet app = H.app(); 
    app.println("[Warning: "+type+" @ "+loc+"]"); 
    if ( msg!=null && msg.length()>0 ) app.println("\t"+msg);
  } 
  private HWarnings() {
  }
} 
public static class HFollow extends HBehavior { 
  private float _ease, _spring, _dx, _dy; 
  private HLocatable _goal; 
  private HLocatable _follower; 
  public HFollow() { 
    this(1);
  } 
  public HFollow(float ease) { 
    this(ease, 0);
  } 
  public HFollow(float ease, float spring) { 
    this(ease, spring, H.mouse());
  } 
  public HFollow(float ease, float spring, HLocatable goal) { 
    _ease = ease; 
    _spring = spring; 
    _goal = goal;
  } 
  public HFollow ease(float f) { 
    _ease = f; 
    return this;
  } 
  public float ease() { 
    return _ease;
  } 
  public HFollow spring(float f) { 
    _spring = f; 
    return this;
  } 
  public float spring() { 
    return _spring;
  } 
  public HFollow goal(HLocatable g) { 
    _goal = g; 
    return this;
  } 
  public HLocatable goal() { 
    return _goal;
  } 
  public HFollow followMouse() { 
    _goal = H.mouse(); 
    return this;
  } 
  public HFollow target(HLocatable f) { 
    if (f == null) unregister(); 
    else register(); 
    _follower = f; 
    return this;
  } 
  public HLocatable target() { 
    return _follower;
  } 
  public void runBehavior(PApplet app) { 
    if (_follower==null || ! H.mouse().started()) return; 
    _dx = _dx*_spring + (_goal.x()-_follower.x()) * _ease; 
    _dy = _dy*_spring + (_goal.y()-_follower.y()) * _ease; 
    _follower.x(_follower.x() + _dx); 
    _follower.y(_follower.y() + _dy);
  } 
  public HFollow register() { 
    return (HFollow) super.register();
  } 
  public HFollow unregister() { 
    return (HFollow) super.unregister();
  }
} 


public static class HMagneticField extends HBehavior { 
  private ArrayList<HPole> _poles; 
  private HLinkedHashSet<HDrawable> _targets; 
  public HMagneticField() { 
    _poles = new ArrayList<HMagneticField.HPole>(); 
    _targets = new HLinkedHashSet<HDrawable>();
  } 
  public HMagneticField addMagnet(float nx, float ny, float sx, float sy) { 
    addPole(nx, ny, 1); 
    addPole(sx, sy, -1); 
    return this;
  } 
  public HMagneticField addPole(float x, float y, float polarity) { 
    HPole p = new HPole(x, y, polarity); 
    _poles.add(p); 
    return this;
  } 
  public HPole pole(int index) { 
    return _poles.get(index);
  } 
  public HMagneticField removePole(int index) { 
    _poles.remove(index); 
    return this;
  } 
  public HMagneticField addTarget(HDrawable d) { 
    if (_targets.size() <= 0) register(); 
    _targets.add(d); 
    return this;
  } 
  public HMagneticField removeTarget(HDrawable d) { 
    _targets.remove(d); 
    if (_targets.size() <= 0) unregister(); 
    return this;
  } 
  public float getRotation(float x, float y) { 
    int poleCount = _poles.size(); 
    PVector v1 = new PVector(0, 0); 
    PVector v2 = new PVector(x, y); 
    PVector distance = new PVector(0, 0); 
    PVector force = new PVector(0, 0); 
    float d = 0; 
    for (int i=0; i<poleCount; i++) { 
      HPole p = _poles.get(i); 
      v1.x = p._x; 
      v1.y = p._y; 
      if (p._polarity < 0) { 
        distance = PVector.sub(v1, v2);
      } 
      else { 
        distance = PVector.sub(v2, v1);
      } 
      d = distance.mag() / 5; 
      distance.normalize(); 
      distance.mult(abs(p._polarity)); 
      distance.div(d); 
      force.add(distance);
    } 
    return atan2(force.y, force.x);
  } 
  public void runBehavior(PApplet app) { 
    for (HDrawable d : _targets) d.rotationRad( getRotation(d.x(), d.y()) );
  } 
  public HMagneticField register() { 
    return (HMagneticField) super.register();
  } 
  public HMagneticField unregister() { 
    return (HMagneticField) super.unregister();
  } 
  public static class HPole { 
    public float _x, _y, _polarity; 
    public HPole(float x, float y, float polarity) { 
      _x = x; 
      _y = y; 
      _polarity = polarity;
    }
  }
} 


public static class HOscillator extends HBehavior { 
  private HDrawable _target; 
  private float _min1, _min2, _min3; 
  private float _rel1, _rel2, _rel3; 
  private float _max1, _max2, _max3; 
  private float _curr1, _curr2, _curr3; 
  private float _origw, _origh; 
  private float _step, _speed, _freq; 
  private int _property, _waveform; 
  public HOscillator() { 
    _speed = _freq = 1; 
    _waveform = HConstants.SINE; 
    _property = HConstants.Y; 
    register();
  } 
  public HOscillator createCopy() { 
    HOscillator copy = new HOscillator(); 
    copy._min1 = _min1; 
    copy._min2 = _min2; 
    copy._min3 = _min3; 
    copy._max1 = _max1; 
    copy._max2 = _max2; 
    copy._max3 = _max3; 
    copy._rel1 = _rel1; 
    copy._rel2 = _rel2; 
    copy._rel3 = _rel3; 
    copy._origw = _origw; 
    copy._origh = _origh; 
    copy._step = _step; 
    copy._speed = _speed; 
    copy._freq = _freq; 
    copy._property = _property; 
    copy._waveform = _waveform; 
    return copy;
  } 
  public HOscillator target(HDrawable d) { 
    _target = d; 
    if (d != null) { 
      _origw = d.width(); 
      _origh = d.height();
    } 
    return this;
  } 
  public HDrawable target() { 
    return _target;
  } 
  public HOscillator currentStep(float stepDegrees) { 
    _step = stepDegrees; 
    return this;
  } 
  public float currentStep() { 
    return _step;
  } 
  public HOscillator speed(float f) { 
    _speed = f; 
    return this;
  } 
  public float speed() { 
    return _speed;
  } 
  public HOscillator range(float minimum, float maximum) { 
    return min(minimum).max(maximum);
  } 
  public HOscillator range( float minA, float minB, float maxA, float maxB ) { 
    return min(minA, minB).max(maxA, maxB);
  } 
  public HOscillator range( float minA, float minB, float minC, float maxA, float maxB, float maxC ) { 
    return min(minA, minB, minC).max(maxA, maxB, maxC);
  } 
  public HOscillator min(float a) { 
    return min(a, a);
  } 
  public HOscillator min(float a, float b) { 
    return min(a, b, 0);
  } 
  public HOscillator min(float a, float b, float c) { 
    _min1 = a; 
    _min2 = b; 
    _min3 = c; 
    return this;
  } 
  public float min() { 
    return _min1;
  } 
  public float min1() { 
    return _min1;
  } 
  public float min2() { 
    return _min2;
  } 
  public float min3() { 
    return _min3;
  } 
  public HOscillator relativeVal(float a) { 
    return relativeVal(a, a);
  } 
  public HOscillator relativeVal(float a, float b) { 
    return relativeVal(a, b, 0);
  } 
  public HOscillator relativeVal(float a, float b, float c) { 
    _rel1 = a; 
    _rel2 = b; 
    _rel3 = c; 
    return this;
  } 
  public float relativeVal() { 
    return _rel1;
  } 
  public float relativeVal1() { 
    return _rel1;
  } 
  public float relativeVal2() { 
    return _rel2;
  } 
  public float relativeVal3() { 
    return _rel3;
  } 
  public HOscillator max(float a) { 
    return max(a, a);
  } 
  public HOscillator max(float a, float b) { 
    return max(a, b, 0);
  } 
  public HOscillator max(float a, float b, float c) { 
    _max1 = a; 
    _max2 = b; 
    _max3 = c; 
    return this;
  } 
  public float max() { 
    return _max1;
  } 
  public float max1() { 
    return _max1;
  } 
  public float max2() { 
    return _max2;
  } 
  public float max3() { 
    return _max3;
  } 
  public HOscillator freq(float f) { 
    _freq = f; 
    return this;
  } 
  public float freq() { 
    return _freq;
  } 
  public HOscillator property(int id) { 
    _property = id; 
    return this;
  } 
  public int property() { 
    return _property;
  } 
  public HOscillator waveform(int waveformId) { 
    _waveform = waveformId; 
    return this;
  } 
  public int waveform() { 
    return _waveform;
  } 
  public float nextRaw() { 
    float deg = (_step*_freq) % 360; 
    float rawVal; 
    switch(_waveform) { 
    case HConstants.SINE: 
      rawVal = HMath.sineWave(deg); 
      break; 
    case HConstants.TRIANGLE: 
      rawVal = HMath.triangleWave(deg); 
      break; 
    case HConstants.SAW: 
      rawVal = HMath.sawWave(deg); 
      break; 
    case HConstants.SQUARE: 
      rawVal = HMath.squareWave(deg); 
      break; 
    default: 
      rawVal = 0; 
      break;
    } 
    _step += _speed; 
    _curr1 = HMath.map(rawVal, -1, 1, _min1, _max1) + _rel1; 
    _curr2 = HMath.map(rawVal, -1, 1, _min2, _max2) + _rel2; 
    _curr3 = HMath.map(rawVal, -1, 1, _min3, _max3) + _rel3; 
    return rawVal;
  } 
  public float curr() { 
    return _curr1;
  } 
  public float curr1() { 
    return _curr1;
  } 
  public float curr2() { 
    return _curr2;
  } 
  public float curr3() { 
    return _curr3;
  } 
  public void runBehavior(PApplet app) { 
    if (_target==null) return; 
    nextRaw(); 
    float v1 = _curr1; 
    float v2 = _curr2; 
    float v3 = _curr3; 
    switch(_property) { 
    case HConstants.WIDTH: 
      _target.width(v1); 
      break; 
    case HConstants.HEIGHT: 
      _target.height(v1); 
      break; 
    case HConstants.SCALE: 
      v1 *= _origw; 
      v2 *= _origh; 
    case HConstants.SIZE: 
      _target.size(v1, v2); 
      break; 
    case HConstants.ALPHA: 
      _target.alpha(Math.round(v1)); 
      break; 
    case HConstants.X: 
      _target.x(v1); 
      break; 
    case HConstants.Y: 
      _target.y(v1); 
      break; 
    case HConstants.Z: 
      _target.z(v1); 
      break; 
    case HConstants.LOCATION: 
      _target.loc(v1, v2, v3); 
      break; 
    case HConstants.ROTATIONX: 
      _target.rotationX(v1); 
      break; 
    case HConstants.ROTATIONY: 
      _target.rotationY(v1); 
      break; 
    case HConstants.ROTATIONZ: 
      _target.rotationZ(v1); 
      break; 
    case HConstants.DROTATIONX: 
      _target.rotateX(v1); 
      break; 
    case HConstants.DROTATIONY: 
      _target.rotateY(v1); 
      break; 
    case HConstants.DROTATIONZ: 
      _target.rotateZ(v1); 
      break; 
    case HConstants.DX: 
      _target.move(v1, 0); 
      break; 
    case HConstants.DY: 
      _target.move(0, v1); 
      break; 
    case HConstants.DLOC: 
      _target.move(v1, v1); 
      break; 
    default: 
      break;
    }
  } 
  public HOscillator register() { 
    return (HOscillator) super.register();
  } 
  public HOscillator unregister() { 
    return (HOscillator) super.unregister();
  }
} 


public static class HRandomTrigger extends HTrigger { 
  private float _chance; 
  public HRandomTrigger() {
  } 
  public HRandomTrigger(float percChance) { 
    _chance = percChance;
  } 
  public HRandomTrigger chance(float chancePercentage) { 
    _chance = chancePercentage; 
    return this;
  } 
  public float chance() { 
    return _chance;
  } 
  public void runBehavior(PApplet app) { 
    if (app.random(1) <= _chance) _callback.run(null);
  } 
  public HRandomTrigger callback(HCallback cb) { 
    return (HRandomTrigger) super.callback(cb);
  }
} 


public static class HRotate extends HBehavior { 
  private HRotatable _target; 
  private float _speedRad; 
  public HRotate() {
  } 
  public HRotate(HRotatable newTarget, float dDeg) { 
    target(newTarget); 
    _speedRad = dDeg * HConstants.D2R;
  } 
  public HRotate target(HRotatable r) { 
    if (r == null) unregister(); 
    else register(); 
    _target = r; 
    return this;
  } 
  public HRotatable target() { 
    return _target;
  } 
  public HRotate speed(float dDeg) { 
    _speedRad = dDeg * HConstants.D2R; 
    return this;
  } 
  public float speed() { 
    return _speedRad * HConstants.R2D;
  } 
  public HRotate speedRad(float dRad) { 
    _speedRad = dRad; 
    return this;
  } 
  public float speedRad() { 
    return _speedRad;
  } 
  public void runBehavior(PApplet app) { 
    float rot = _target.rotationRad() + _speedRad; 
    _target.rotationRad(rot);
  } 
  public HRotate register() { 
    return (HRotate) super.register();
  } 
  public HRotate unregister() { 
    return (HRotate) super.unregister();
  }
} 


public static class HSwarm extends HBehavior { 
  private HLinkedHashSet<HLocatable> _goals; 
  private HLinkedHashSet<HDirectable> _targets; 
  private float _speed, _turnEase, _twitchRad, _idleGoalX, _idleGoalY; 
  public HSwarm() { 
    _speed = 1; 
    _turnEase = 1; 
    _twitchRad = 0; 
    _goals = new HLinkedHashSet<HLocatable>(); 
    _targets = new HLinkedHashSet<HDirectable>();
  } 
  public HSwarm addTarget(HDirectable t) { 
    if (_targets.size() <= 0) register(); 
    _targets.add(t); 
    return this;
  } 
  public HSwarm removeTarget(HDirectable t) { 
    _targets.remove(t); 
    if (_targets.size() <= 0) unregister(); 
    return this;
  } 
  public HLinkedHashSet<HDirectable> targets() { 
    return _targets;
  } 
  public HSwarm addGoal(HLocatable g) { 
    _goals.add(g); 
    return this;
  } 
  public HSwarm addGoal(float x, float y) { 
    return addGoal(new HVector(x, y));
  } 
  public HSwarm addGoal(float x, float y, float z) { 
    return addGoal(new HVector(x, y, z));
  } 
  public HSwarm removeGoal(HLocatable g) { 
    _goals.remove(g); 
    return this;
  } 
  public HLinkedHashSet<HLocatable> goals() { 
    return _goals;
  } 
  public HSwarm idleGoal(float x, float y) { 
    _idleGoalX = x; 
    _idleGoalY = y; 
    return this;
  } 
  public float idleGoalX() { 
    return _idleGoalX;
  } 
  public float idleGoalY() { 
    return _idleGoalY;
  } 
  public HSwarm speed(float s) { 
    _speed = s; 
    return this;
  } 
  public float speed() { 
    return _speed;
  } 
  public HSwarm turnEase(float e) { 
    _turnEase = e; 
    return this;
  } 
  public float turnEase() { 
    return _turnEase;
  } 
  public HSwarm twitch(float deg) { 
    _twitchRad = deg * HConstants.D2R; 
    return this;
  } 
  public HSwarm twitchRad(float rad) { 
    _twitchRad = rad; 
    return this;
  } 
  public float twitch() { 
    return _twitchRad * HConstants.R2D;
  } 
  public float twitchRad() { 
    return _twitchRad;
  } 
  private HLocatable getGoal(HDirectable target, PApplet app) { 
    HLocatable goal = null; 
    float nearestDist = -1; 
    for (HLocatable h : _goals) { 
      float dist = HMath.dist(target.x(), target.y(), h.x(), h.y()); 
      if (nearestDist<0 || dist<nearestDist) { 
        nearestDist = dist; 
        goal = h;
      }
    } 
    return goal;
  } 
  public void runBehavior(PApplet app) { 
    int numTargets = _targets.size(); 
    Iterator<HDirectable> it = _targets.iterator(); 
    for (int i=0; i<numTargets; ++i) { 
      HDirectable target = it.next(); 
      float rot = target.rotationRad(); 
      float tx = target.x(); 
      float ty = target.y(); 
      float goalx = _idleGoalX; 
      float goaly = _idleGoalY; 
      float goalz = 0; 
      HLocatable goal = getGoal(target, app); 
      if (goal != null) { 
        goalx = goal.x(); 
        goaly = goal.y(); 
        goalz = goal.z();
      } 
      float tmp = HMath.xAxisAngle(tx, ty, goalx, goaly) - rot; 
      float dRot = _turnEase * (float) Math.atan2( Math.sin(tmp), Math.cos(tmp) ); 
      rot += dRot; 
      float noise = app.noise(i*numTargets + app.frameCount/8f); 
      rot += HMath.map(noise, 0, 1, -_twitchRad, _twitchRad); 
      target.rotationRad(rot); 
      target.x(target.x() + (float)Math.cos(rot)*_speed); 
      target.y(target.y() + (float)Math.sin(rot)*_speed); 
      target.z(goalz);
    }
  } 
  public HSwarm register() { 
    return (HSwarm) super.register();
  } 
  public HSwarm unregister() { 
    return (HSwarm) super.unregister();
  }
} 


public static class HTimer extends HTrigger { 
  private int _lastInterval, _interval, _cycleCounter, _numCycles; 
  private boolean _usesFrames; 
  public HTimer() { 
    _interval = 1000; 
    _lastInterval = -1;
  } 
  public HTimer(int timerInterval) { 
    _interval = timerInterval;
  } 
  public HTimer(int timerInterval, int numberOfCycles) { 
    _interval = timerInterval; 
    _numCycles = numberOfCycles;
  } 
  public HTimer interval(int i) { 
    _interval = i; 
    return this;
  } 
  public int interval() { 
    return _interval;
  } 
  public HTimer cycleCounter(int cycleIndex) { 
    _cycleCounter = cycleIndex; 
    return this;
  } 
  public int cycleCounter() { 
    return _cycleCounter;
  } 
  public HTimer numCycles(int cycles) { 
    _numCycles = cycles; 
    return this;
  } 
  public int numCycles() { 
    return _numCycles;
  } 
  public HTimer cycleIndefinitely() { 
    _numCycles = 0; 
    return this;
  } 
  public HTimer useMillis() { 
    _usesFrames = false; 
    return this;
  } 
  public boolean usesMillis() { 
    return !_usesFrames;
  } 
  public HTimer useFrames() { 
    _usesFrames = true; 
    return this;
  } 
  public boolean usesFrames() { 
    return _usesFrames;
  } 
  public void runBehavior(PApplet app) { 
    int curr = (_usesFrames)? app.frameCount : app.millis(); 
    if (_lastInterval < 0) _lastInterval = curr; 
    if (curr-_lastInterval >= _interval) { 
      _lastInterval = curr; 
      _callback.run(_cycleCounter); 
      if (_numCycles > 0 && ++_cycleCounter >= _numCycles) unregister();
    }
  } 
  public HTimer callback(HCallback cb) { 
    return (HTimer) super.callback(cb);
  } 
  public HTimer register() { 
    return (HTimer) super.register();
  } 
  public HTimer unregister() { 
    _numCycles = 0; 
    _lastInterval = -1; 
    return (HTimer) super.unregister();
  }
} 


public static class HTween extends HBehavior { 
  private HDrawable _target; 
  private HCallback _callback; 
  private float _s1, _s2, _s3; 
  private float _e1, _e2, _e3; 
  private float _curr1, _curr2, _curr3; 
  private float _origw, _origh; 
  private float _raw, _dRaw, _spring, _ease; 
  private int _property; 
  public HTween() { 
    _ease = 1; 
    _callback = HConstants.NOP; 
    register();
  } 
  public HTween target(HDrawable d) { 
    _target = d; 
    if (d != null) { 
      _origw = d.width(); 
      _origh = d.height();
    } 
    return this;
  } 
  public HDrawable target() { 
    return _target;
  } 
  public HTween callback(HCallback c) { 
    _callback = (c==null)? HConstants.NOP : c; 
    return this;
  } 
  public HCallback callback() { 
    return _callback;
  } 
  public HTween start(float a) { 
    return start(a, a);
  } 
  public HTween start(float a, float b) { 
    return start(a, b, 0);
  } 
  public HTween start(float a, float b, float c) { 
    _s1 = a; 
    _s2 = b; 
    _s3 = c; 
    return this;
  } 
  public float start() { 
    return _s1;
  } 
  public float start1() { 
    return _s1;
  } 
  public float start2() { 
    return _s2;
  } 
  public float start3() { 
    return _s3;
  } 
  public HTween end(float a) { 
    return end(a, a);
  } 
  public HTween end(float a, float b) { 
    return end(a, b, 0);
  } 
  public HTween end(float a, float b, float c) { 
    _e1 = a; 
    _e2 = b; 
    _e3 = c; 
    return this;
  } 
  public float end() { 
    return _e1;
  } 
  public float end1() { 
    return _e1;
  } 
  public float end2() { 
    return _e2;
  } 
  public float end3() { 
    return _e3;
  } 
  public HTween spring(float f) { 
    _spring = f; 
    return this;
  } 
  public float spring() { 
    return _spring;
  } 
  public HTween ease(float f) { 
    _ease = f; 
    return this;
  } 
  public float ease() { 
    return _ease;
  } 
  public HTween property(int id) { 
    _property = id; 
    return this;
  } 
  public int property() { 
    return _property;
  } 
  public float nextRaw() { 
    _raw += (_dRaw) = (_dRaw*_spring + (1-_raw)*_ease); 
    float c = HMath.round512(_raw); 
    _curr1 = HMath.map(c, 0, 1, _s1, _e1); 
    _curr2 = HMath.map(c, 0, 1, _s2, _e2); 
    _curr3 = HMath.map(c, 0, 1, _s3, _e3); 
    return c;
  } 
  public float curr() { 
    return _curr1;
  } 
  public float curr1() { 
    return _curr1;
  } 
  public float curr2() { 
    return _curr2;
  } 
  public float curr3() { 
    return _curr3;
  } 
  public void runBehavior(PApplet app) { 
    if (_target==null) return; 
    float c = nextRaw(); 
    float v1 = _curr1; 
    float v2 = _curr2; 
    float v3 = _curr3; 
    switch(_property) { 
    case HConstants.WIDTH: 
      _target.width(v1); 
      break; 
    case HConstants.HEIGHT: 
      _target.height(v1); 
      break; 
    case HConstants.SCALE: 
      v1 *= _origw; 
      v2 *= _origh; 
    case HConstants.SIZE: 
      _target.size(v1, v2); 
      break; 
    case HConstants.ALPHA: 
      _target.alpha(Math.round(v1)); 
      break; 
    case HConstants.X: 
      _target.x(v1); 
      break; 
    case HConstants.Y: 
      _target.y(v1); 
      break; 
    case HConstants.Z: 
      _target.z(v1); 
      break; 
    case HConstants.LOCATION: 
      _target.loc(v1, v2, v3); 
      break; 
    case HConstants.ROTATIONX: 
      _target.rotationX(v1); 
      break; 
    case HConstants.ROTATIONY: 
      _target.rotationY(v1); 
      break; 
    case HConstants.ROTATIONZ: 
      _target.rotationZ(v1); 
      break; 
    case HConstants.DROTATIONX: 
      _target.rotateX(v1); 
      break; 
    case HConstants.DROTATIONY: 
      _target.rotateY(v1); 
      break; 
    case HConstants.DROTATIONZ: 
      _target.rotateZ(v1); 
      break; 
    case HConstants.DX: 
      _target.move(v1, 0); 
      break; 
    case HConstants.DY: 
      _target.move(0, v1); 
      break; 
    case HConstants.DLOC: 
      _target.move(v1, v1); 
      break; 
    default: 
      break;
    } 
    if (c==1 && HMath.round512(_dRaw)==0) { 
      unregister(); 
      _callback.run(this);
    }
  } 
  public HTween register() { 
    return (HTween) super.register();
  } 
  public HTween unregister() { 
    _raw = _dRaw = 0; 
    return (HTween) super.unregister();
  }
} 


public static class HVelocity extends HBehavior { 
  private float _velocityX, _velocityY, _accelX, _accelY; 
  private HLocatable _target; 
  public HVelocity target(HLocatable t) { 
    if (t == null) unregister(); 
    else register(); 
    _target = t; 
    return this;
  } 
  public HLocatable target() { 
    return _target;
  } 
  public HVelocity velocity(float velocity, float deg) { 
    return velocityRad(velocity, deg*HConstants.D2R);
  } 
  public HVelocity velocityRad(float velocity, float rad) { 
    _velocityX = velocity * (float)Math.cos(rad); 
    _velocityY = velocity * (float)Math.sin(rad); 
    return this;
  } 
  public HVelocity velocityX(float dx) { 
    _velocityX = dx; 
    return this;
  } 
  public float velocityX() { 
    return _velocityX;
  } 
  public HVelocity velocityY(float dy) { 
    _velocityY = dy; 
    return this;
  } 
  public float velocityY() { 
    return _velocityY;
  } 
  public HVelocity launchTo(float goalX, float goalY, int numFrames) { 
    if (_target == null) { 
      HWarnings.warn("Null Target", "HVelocity.launchTo()", HWarnings.NULL_TARGET);
    } 
    else { 
      float nfsq = numFrames*numFrames; 
      _velocityX = (goalX - _target.x() - _accelX*nfsq/2) / numFrames; 
      _velocityY = (goalY - _target.y() - _accelY*nfsq/2) / numFrames;
    } 
    return this;
  } 
  public HVelocity accel(float acceleration, float deg) { 
    return accelRad(acceleration, deg*HConstants.D2R);
  } 
  public HVelocity accelRad(float acceleration, float rad) { 
    _accelX = acceleration * (float)Math.cos(rad); 
    _accelY = acceleration * (float)Math.sin(rad); 
    return this;
  } 
  public HVelocity accelX(float ddx) { 
    _accelX = ddx; 
    return this;
  } 
  public float accelX() { 
    return _accelX;
  } 
  public HVelocity accelY(float ddy) { 
    _accelY = ddy; 
    return this;
  } 
  public float accelY() { 
    return _accelY;
  } 
  public void runBehavior(PApplet app) { 
    _target.x(_target.x() + _velocityX); 
    _target.y(_target.y() + _velocityY); 
    _velocityX += _accelX; 
    _velocityY += _accelY;
  } 
  public HVelocity register() { 
    return (HVelocity) super.register();
  } 
  public HVelocity unregister() { 
    return (HVelocity) super.unregister();
  }
} 


public static class HColorField implements HColorist { 
  private ArrayList<HColorPoint> _colorPoints; 
  private float _maxDist; 
  private boolean _appliesFill, _appliesStroke, _appliesAlpha; 
  public HColorField() { 
    this(H.app().width, H.app().height);
  } 
  public HColorField(float xBound, float yBound) { 
    this( (float) Math.sqrt(xBound*xBound + yBound*yBound) );
  } 
  public HColorField(float maximumDistance) { 
    _colorPoints = new ArrayList<HColorField.HColorPoint>(); 
    _maxDist = maximumDistance; 
    fillAndStroke();
  } 
  public HColorField addPoint(PVector loc, int clr, float radius) { 
    return addPoint(loc.x, loc.y, clr, radius);
  } 
  public HColorField addPoint(float x, float y, int clr, float radius) { 
    HColorPoint pt = new HColorPoint(); 
    pt.x = x; 
    pt.y = y; 
    pt.radius = radius; 
    pt.clr = clr; 
    _colorPoints.add(pt); 
    return this;
  } 
  public int getColor(float x, float y, int baseColor) { 
    int[] baseClrs = HColors.explode(baseColor); 
    int[] maxClrs = new int[4]; 
    int initJ; 
    if (_appliesAlpha) { 
      initJ = 0;
    } 
    else { 
      initJ = 1; 
      maxClrs[0] = baseClrs[0];
    } 
    for (int i=0; i<_colorPoints.size(); ++i) { 
      HColorPoint pt = _colorPoints.get(i); 
      int[] ptClrs = HColors.explode(pt.clr); 
      float distLimit = _maxDist * pt.radius; 
      float dist = HMath.dist(x, y, pt.x, pt.y); 
      if (dist > distLimit) dist = distLimit; 
      for (int j=initJ; j<4; ++j) { 
        int newClrVal = Math.round( HMath.map(dist, 0, distLimit, ptClrs[j], baseClrs[j])); 
        if (newClrVal > maxClrs[j]) maxClrs[j] = newClrVal;
      }
    } 
    return HColors.merge(maxClrs[0], maxClrs[1], maxClrs[2], maxClrs[3]);
  } 
  public HColorField appliesAlpha(boolean b) { 
    _appliesAlpha = b; 
    return this;
  } 
  public boolean appliesAlpha() { 
    return _appliesAlpha;
  } 
  public HColorField fillOnly() { 
    _appliesFill = true; 
    _appliesStroke = false; 
    return this;
  } 
  public HColorField strokeOnly() { 
    _appliesFill = false; 
    _appliesStroke = true; 
    return this;
  } 
  public HColorField fillAndStroke() { 
    _appliesFill = _appliesStroke = true; 
    return this;
  } 
  public boolean appliesFill() { 
    return _appliesFill;
  } 
  public boolean appliesStroke() { 
    return _appliesStroke;
  } 
  public HDrawable applyColor(HDrawable drawable) { 
    float x = drawable.x(); 
    float y = drawable.y(); 
    if (_appliesFill) { 
      int baseFill = drawable.fill(); 
      drawable.fill( getColor(x, y, baseFill) );
    } 
    if (_appliesStroke) { 
      int baseStroke = drawable.stroke(); 
      drawable.stroke( getColor(x, y, baseStroke) );
    } 
    return drawable;
  } 
  public static class HColorPoint { 
    public float x, y, radius; 
    public int clr;
  }
} 


public static class HColorPool implements HColorist { 
  private ArrayList<Integer> _colorList; 
  private boolean _fillFlag, _strokeFlag; 
  public HColorPool(int... colors) { 
    _colorList = new ArrayList<Integer>(); 
    for (int i=0; i<colors.length; ++i) add(colors[i]); 
    fillAndStroke();
  } 
  public HColorPool createCopy() { 
    HColorPool copy = new HColorPool(); 
    copy._fillFlag = _fillFlag; 
    copy._strokeFlag = _strokeFlag; 
    for (int i=0; i<_colorList.size(); ++i) { 
      int clr = _colorList.get(i); 
      copy._colorList.add( clr );
    } 
    return copy;
  } 
  public int size() { 
    return _colorList.size();
  } 
  public HColorPool add(int clr) { 
    _colorList.add(clr); 
    return this;
  } 
  public HColorPool add(int clr, int freq) { 
    while (freq-- > 0) _colorList.add(clr); 
    return this;
  } 
  public int getColor() { 
    if (_colorList.size() <= 0) return 0; 
    int index = (int) Math.floor(H.app().random(_colorList.size())); 
    return _colorList.get(index);
  } 
  public int getColor(int seed) { 
    HMath.tempSeed(seed); 
    int clr = getColor(); 
    HMath.removeTempSeed(); 
    return clr;
  } 
  public HColorPool fillOnly() { 
    _fillFlag = true; 
    _strokeFlag = false; 
    return this;
  } 
  public HColorPool strokeOnly() { 
    _fillFlag = false; 
    _strokeFlag = true; 
    return this;
  } 
  public HColorPool fillAndStroke() { 
    _fillFlag = _strokeFlag = true; 
    return this;
  } 
  public boolean appliesFill() { 
    return _fillFlag;
  } 
  public boolean appliesStroke() { 
    return _strokeFlag;
  } 
  public HDrawable applyColor(HDrawable drawable) { 
    if (_fillFlag) drawable.fill(getColor()); 
    if (_strokeFlag) drawable.stroke(getColor()); 
    return drawable;
  }
} 


public static class HColorTransform implements HColorist { 
  private float _percA, _percR, _percG, _percB; 
  private int _offsetA, _offsetR, _offsetG, _offsetB; 
  private boolean fillFlag, strokeFlag; 
  public HColorTransform() { 
    _percA = _percR = _percG = _percB = 1; 
    fillAndStroke();
  } 
  public HColorTransform offset(int off) { 
    _offsetA = _offsetR = _offsetG = _offsetB = off; 
    return this;
  } 
  public HColorTransform offset(int r, int g, int b, int a) { 
    _offsetA = a; 
    _offsetR = r; 
    _offsetG = g; 
    _offsetB = b; 
    return this;
  } 
  public HColorTransform offsetA(int a) { 
    _offsetA = a; 
    return this;
  } 
  public int offsetA() { 
    return _offsetA;
  } 
  public HColorTransform offsetR(int r) { 
    _offsetR = r; 
    return this;
  } 
  public int offsetR() { 
    return _offsetR;
  } 
  public HColorTransform offsetG(int g) { 
    _offsetG = g; 
    return this;
  } 
  public int offsetG() { 
    return _offsetG;
  } 
  public HColorTransform offsetB(int b) { 
    _offsetB = b; 
    return this;
  } 
  public int offsetB() { 
    return _offsetB;
  } 
  public HColorTransform perc(float percentage) { 
    _percA = _percR = _percG = _percB = percentage; 
    return this;
  } 
  public HColorTransform perc(int r, int g, int b, int a) { 
    _percA = a; 
    _percR = r; 
    _percG = g; 
    _percB = b; 
    return this;
  } 
  public HColorTransform percA(float a) { 
    _percA = a; 
    return this;
  } 
  public float percA() { 
    return _percA;
  } 
  public HColorTransform percR(float r) { 
    _percR = r; 
    return this;
  } 
  public float percR() { 
    return _percR;
  } 
  public HColorTransform percG(float g) { 
    _percG = g; 
    return this;
  } 
  public float percG() { 
    return _percG;
  } 
  public HColorTransform percB(float b) { 
    _percB = b; 
    return this;
  } 
  public float percB() { 
    return _percB;
  } 
  public HColorTransform mergeWith(HColorTransform other) { 
    if (other != null) { 
      _percA *= other._percA; 
      _percR *= other._percR; 
      _percG *= other._percG; 
      _percB *= other._percB; 
      _offsetA += other._offsetA; 
      _offsetR += other._offsetR; 
      _offsetG += other._offsetG; 
      _offsetB += other._offsetB;
    } 
    return this;
  } 
  public HColorTransform createCopy() { 
    HColorTransform copy = new HColorTransform(); 
    copy._percA = _percA; 
    copy._percR = _percR; 
    copy._percG = _percG; 
    copy._percB = _percB; 
    copy._offsetA = _offsetA; 
    copy._offsetR = _offsetR; 
    copy._offsetG = _offsetG; 
    copy._offsetB = _offsetB; 
    return copy;
  } 
  public HColorTransform createNew(HColorTransform other) { 
    return createCopy().mergeWith(other);
  } 
  public int getColor(int origColor) { 
    int[] clrs = HColors.explode(origColor); 
    clrs[0] = Math.round(clrs[0] * _percA) + _offsetA; 
    clrs[1] = Math.round(clrs[1] * _percR) + _offsetR; 
    clrs[2] = Math.round(clrs[2] * _percG) + _offsetG; 
    clrs[3] = Math.round(clrs[3] * _percB) + _offsetB; 
    return HColors.merge(clrs[0], clrs[1], clrs[2], clrs[3]);
  } 
  public HColorTransform fillOnly() { 
    fillFlag = true; 
    strokeFlag = false; 
    return this;
  } 
  public HColorTransform strokeOnly() { 
    fillFlag = false; 
    strokeFlag = true; 
    return this;
  } 
  public HColorTransform fillAndStroke() { 
    fillFlag = strokeFlag = true; 
    return this;
  } 
  public boolean appliesFill() { 
    return fillFlag;
  } 
  public boolean appliesStroke() { 
    return strokeFlag;
  } 
  public HDrawable applyColor(HDrawable drawable) { 
    if (fillFlag) { 
      int fill = drawable.fill(); 
      drawable.fill( getColor(fill) );
    } 
    if (strokeFlag) { 
      int stroke = drawable.stroke(); 
      drawable.stroke( getColor(stroke) );
    } 
    return drawable;
  }
} 


public static class HPixelColorist implements HColorist, HImageHolder { 
  private PImage img; 
  private boolean fillFlag, strokeFlag; 
  public HPixelColorist() { 
    fillAndStroke();
  } 
  public HPixelColorist(Object imgArg) { 
    this(); 
    image(imgArg);
  } 
  public HPixelColorist image(Object imgArg) { 
    img = H.getImage(imgArg); 
    return this;
  } 
  public PImage image() { 
    return img;
  } 
  public HPixelColorist setImage(Object imgArg) { 
    if (imgArg instanceof PImage) { 
      img = (PImage) imgArg;
    } 
    else if (imgArg instanceof HImage) { 
      img = ((HImage) imgArg).image();
    } 
    else if (imgArg instanceof String) { 
      img = H.app().loadImage((String) imgArg);
    } 
    else if (imgArg == null) { 
      img = null;
    } 
    return this;
  } 
  public PImage getImage() { 
    return img;
  } 
  public int getColor(float x, float y) { 
    return (img==null)? 0 : img.get(Math.round(x), Math.round(y));
  } 
  public HPixelColorist fillOnly() { 
    fillFlag = true; 
    strokeFlag = false; 
    return this;
  } 
  public HPixelColorist strokeOnly() { 
    fillFlag = false; 
    strokeFlag = true; 
    return this;
  } 
  public HPixelColorist fillAndStroke() { 
    fillFlag = strokeFlag = true; 
    return this;
  } 
  public boolean appliesFill() { 
    return fillFlag;
  } 
  public boolean appliesStroke() { 
    return strokeFlag;
  } 
  public HDrawable applyColor(HDrawable drawable) { 
    int clr = getColor(drawable.x(), drawable.y()); 
    if (fillFlag) drawable.fill(clr); 
    if (strokeFlag) drawable.stroke(clr); 
    return drawable;
  }
} 


public static class HBox extends HDrawable3D { 
  public HDrawable createCopy() { 
    HBox copy = new HBox(); 
    copy.copyPropertiesFrom(this); 
    copy._depth = _depth; 
    copy._anchorW = _anchorW; 
    return copy;
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float currAlphaPc ) { 
    applyStyle(g, currAlphaPc); 
    g.pushMatrix(); 
    g.translate(drawX, drawY, -anchorZ()); 
    g.box(_width, _height, _depth); 
    g.popMatrix();
  }
} 


public static class HCanvas extends HDrawable { 
  private PGraphics _graphics; 
  private String _renderer; 
  private float _filterParam; 
  private int _filterKind, _blendMode, _fadeAmt; 
  private boolean _autoClear, _hasFade, _hasFilter, _hasFilterParam, _hasBlend; 
  public HCanvas() { 
    this(H.app().width, H.app().height);
  } 
  public HCanvas(String bufferRenderer) { 
    this(H.app().width, H.app().height, bufferRenderer);
  } 
  public HCanvas(float w, float h) { 
    this(w, h, PConstants.JAVA2D);
  } 
  public HCanvas(float w, float h, String bufferRenderer) { 
    _renderer = bufferRenderer; 
    size(w, h);
  } 
  public HCanvas createCopy() { 
    HCanvas copy = new HCanvas(_width, _height, _renderer); 
    copy.autoClear(_autoClear).hasFade(_hasFade); 
    if (_hasFilter) copy.filter(_filterKind, _filterParam); 
    if (_hasBlend) copy.blend(_blendMode); 
    copy.copyPropertiesFrom(this); 
    return copy;
  } 
  protected void updateBuffer() { 
    int w = Math.round(_width); 
    int h = Math.round(_height); 
    _graphics = H.app().createGraphics(w, h, _renderer); 
    _graphics.loadPixels(); 
    _graphics.beginDraw(); 
    _graphics.background(H.CLEAR); 
    _graphics.endDraw(); 
    _width = w; 
    _height = h;
  } 
  public HCanvas renderer(String s) { 
    _renderer = s; 
    updateBuffer(); 
    return this;
  } 
  public String renderer() { 
    return _renderer;
  } 
  public boolean usesZ() { 
    return _renderer.equals(PConstants.P3D) || _renderer.equals(PConstants.OPENGL);
  } 
  public PGraphics graphics() { 
    return _graphics;
  } 
  public HCanvas filter(int kind) { 
    _hasFilter = true; 
    _hasFilterParam = false; 
    _filterKind = kind; 
    return this;
  } 
  public HCanvas filter(int kind, float param) { 
    _hasFilter = true; 
    _hasFilterParam = true; 
    _filterKind = kind; 
    _filterParam = param; 
    return this;
  } 
  public HCanvas noFilter() { 
    _hasFilter = false; 
    return this;
  } 
  public boolean hasFilter() { 
    return _hasFilter;
  } 
  public HCanvas filterKind(int i) { 
    _filterKind = i; 
    return this;
  } 
  public int filterKind() { 
    return _filterKind;
  } 
  public HCanvas filterParam(float f) { 
    _filterParam = f; 
    return this;
  } 
  public float filterParam() { 
    return _filterParam;
  } 
  public HCanvas blend() { 
    return blend(PConstants.BLEND);
  } 
  public HCanvas blend(int mode) { 
    _hasBlend = true; 
    _blendMode = mode; 
    return this;
  } 
  public HCanvas noBlend() { 
    _hasBlend = false; 
    return this;
  } 
  public HCanvas hasBlend(boolean b) { 
    return (b)? blend() : noBlend();
  } 
  public boolean hasBlend() { 
    return _hasBlend;
  } 
  public HCanvas blendMode(int i) { 
    _blendMode = i; 
    return this;
  } 
  public int blendMode() { 
    return _blendMode;
  } 
  public HCanvas fade(int fadeAmt) { 
    _hasFade = true; 
    _fadeAmt = fadeAmt; 
    return this;
  } 
  public HCanvas noFade() { 
    _hasFade = false; 
    return this;
  } 
  public HCanvas hasFade(boolean b) { 
    _hasFade = b; 
    return this;
  } 
  public boolean hasFade() { 
    return _hasFade;
  } 
  public HCanvas autoClear(boolean b) { 
    _autoClear = b; 
    return this;
  } 
  public boolean autoClear() { 
    return _autoClear;
  } 
  public HCanvas background(int clr) { 
    return (HCanvas) fill(clr);
  } 
  public HCanvas background(int clr, int alpha) { 
    return (HCanvas) fill(clr, alpha);
  } 
  public HCanvas background(int r, int g, int b) { 
    return (HCanvas) fill(r, g, b);
  } 
  public HCanvas background(int r, int g, int b, int a) { 
    return (HCanvas) fill(r, g, b, a);
  } 
  public int background() { 
    return _fill;
  } 
  public HCanvas noBackground() { 
    return (HCanvas) noFill();
  } 
  public HCanvas size(float w, float h) { 
    super.width(w); 
    super.height(h); 
    updateBuffer(); 
    return this;
  } 
  public HCanvas width(float w) { 
    super.width(w); 
    updateBuffer(); 
    return this;
  } 
  public HCanvas height(float h) { 
    super.height(h); 
    updateBuffer(); 
    return this;
  } 
  public void paintAll(PGraphics g, boolean zFlag, float alphaPc) { 
    if (_alphaPc<=0 || _width==0 || _height==0) return; 
    g.pushMatrix(); 
    if (zFlag) g.translate(_x, _y, _z); 
    else g.translate(_x, _y); 
    g.rotate(_rotationZRad); 
    alphaPc *= _alphaPc; 
    _graphics.beginDraw(); 
    if (_autoClear) { 
      _graphics.clear();
    } 
    else { 
      if (_hasFilter) { 
        if (_hasFilterParam) _graphics.filter(_filterKind, _filterParam); 
        else _graphics.filter(_filterKind);
      } 
      if (_hasFade) { 
        if (!_renderer.equals(PConstants.JAVA2D)) _graphics.loadPixels(); 
        int[] pix = _graphics.pixels; 
        for (int i=0; i<pix.length; ++i) { 
          int clr = pix[i]; 
          int a = clr >>> 24; 
          if (a == 0) continue; 
          a -= _fadeAmt; 
          if (a < 0) a = 0; 
          pix[i] = clr & 0xFFFFFF | (a << 24);
        } 
        _graphics.updatePixels();
      } 
      if (_hasBlend) { 
        _graphics.blend( 0, 0, _graphics.width, _graphics.height, 0, 0, _graphics.width, _graphics.height, _blendMode);
      }
    } 
    HDrawable child = _firstChild; 
    while (child != null) { 
      child.paintAll(_graphics, usesZ(), alphaPc); 
      child = child.next();
    } 
    _graphics.endDraw(); 
    g.image(_graphics, 0, 0); 
    g.popMatrix();
  } 
  public void draw(PGraphics g, boolean b, float x, float y, float f) {
  }
} 


public static class HEllipse extends HDrawable { 
  private int _mode; 
  private float _startRad, _endRad; 
  public HEllipse() { 
    _mode = PConstants.PIE;
  } 
  public HEllipse(float ellipseRadius) { 
    this(); 
    radius(ellipseRadius);
  } 
  public HEllipse(float radiusX, float radiusY) { 
    this(); 
    radius(radiusX, radiusY);
  } 
  public HEllipse createCopy() { 
    HEllipse copy = new HEllipse(); 
    copy.copyPropertiesFrom(this); 
    return copy;
  } 
  public HEllipse radius(float r) { 
    size(r*2); 
    return this;
  } 
  public HEllipse radius(float radiusX, float radiusY) { 
    size(radiusX*2, radiusY*2); 
    return this;
  } 
  public HEllipse radiusX(float radiusX) { 
    width(radiusX * 2); 
    return this;
  } 
  public float radiusX() { 
    return _width/2;
  } 
  public HEllipse radiusY(float radiusY) { 
    height(radiusY * 2); 
    return this;
  } 
  public float radiusY() { 
    return _height/2;
  } 
  public boolean isCircle() { 
    return _width == _height;
  } 
  public HEllipse mode(int t) { 
    _mode = t; 
    return this;
  } 
  public float mode() { 
    return _mode;
  } 
  public HEllipse start(float deg) { 
    return startRad(deg * H.D2R);
  } 
  public float start() { 
    return _startRad * H.R2D;
  } 
  public HEllipse startRad(float rad) { 
    _startRad = HMath.normalizeAngleRad(rad); 
    if (_startRad > _endRad) _endRad += PConstants.TWO_PI; 
    return this;
  } 
  public float startRad() { 
    return _startRad;
  } 
  public HEllipse end(float deg) { 
    return endRad(deg * H.D2R);
  } 
  public float end() { 
    return _endRad * H.R2D;
  } 
  public HEllipse endRad(float rad) { 
    _endRad = HMath.normalizeAngleRad(rad); 
    if (_startRad > _endRad) _endRad += PConstants.TWO_PI; 
    return this;
  } 
  public float endRad() { 
    return _endRad;
  } 
  public boolean containsRel(float relX, float relY) { 
    float cx = _width/2; 
    float cy = _height/2; 
    float dcx = relX - cx; 
    float dcy = relY - cy; 
    boolean inEllipse = ((dcx*dcx)/(cx*cx) + (dcy*dcy)/(cy*cy) <= 1); 
    if (_startRad == _endRad) return inEllipse; 
    else if (!inEllipse) return false; 
    if (_mode == PConstants.PIE) { 
      float ptAngle = (float) Math.atan2(dcy*cx, dcx*cy); 
      if (_startRad > ptAngle) ptAngle += PConstants.TWO_PI; 
      return (_startRad<=ptAngle && ptAngle<=_endRad);
    } 
    else { 
      float end = HMath.squishAngleRad(cx, cy, _endRad); 
      float start = HMath.squishAngleRad(cx, cy, _startRad); 
      float[] pt1 = HMath.ellipsePointRadArr(cx, cy, cx, cy, end); 
      float[] pt2 = HMath.ellipsePointRadArr(cx, cy, cx, cy, start); 
      return HMath.rightOfLine(pt1[0], pt1[1], pt2[0], pt2[1], relX, relY);
    }
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float alphaPc ) { 
    applyStyle(g, alphaPc); 
    drawX += _width/2; 
    drawY += _height/2; 
    if (_startRad == _endRad) { 
      g.ellipse(drawX, drawY, _width, _height);
    } 
    else { 
      g.arc(drawX, drawY, _width, _height, _startRad, _endRad, _mode);
    }
  }
} 


public static class HGroup extends HDrawable { 
  public HGroup() { 
    transformsChildren(true).stylesChildren(true);
  } 
  public HGroup createCopy() { 
    HGroup copy = new HGroup(); 
    copy.copyPropertiesFrom(this); 
    return copy;
  } 
  public void paintAll(PGraphics g, boolean usesZ, float alphaPc) { 
    if (_alphaPc<=0) return; 
    g.pushMatrix(); 
    if (usesZ) g.translate(_x, _y, _z); 
    else g.translate(_x, _y); 
    g.rotate(_rotationZRad); 
    alphaPc *= _alphaPc; 
    HDrawable child = _firstChild; 
    while (child != null) { 
      child.paintAll(g, usesZ, alphaPc); 
      child = child.next();
    } 
    g.popMatrix();
  } 
  public void draw(PGraphics g, boolean b, float x, float y, float f) {
  }
} 


public static class HImage extends HDrawable implements HImageHolder { 
  private PImage _image; 
  public HImage() { 
    this(null);
  } 
  public HImage(Object imgArg) { 
    image(imgArg);
  } 
  public HImage createCopy() { 
    HImage copy = new HImage(_image); 
    copy.copyPropertiesFrom(this); 
    return copy;
  } 
  public HImage resetSize() { 
    if (_image == null) size(0f, 0f); 
    else size(_image.width, _image.height); 
    return this;
  } 
  public HImage image(Object imgArg) { 
    _image = H.getImage(imgArg); 
    return resetSize();
  } 
  public PImage image() { 
    return _image;
  } 
  public HImage tint(int clr) { 
    fill(clr); 
    return this;
  } 
  public HImage tint(int clr, int alpha) { 
    fill(clr, alpha); 
    return this;
  } 
  public HImage tint(int r, int g, int b) { 
    fill(r, g, b); 
    return this;
  } 
  public HImage tint(int r, int g, int b, int a) { 
    fill(r, g, b, a); 
    return this;
  } 
  public int tint() { 
    return fill();
  } 
  public boolean containsRel(float relX, float relY) { 
    if (_image == null || _image.width <= 0 || _image.height <= 0 || _width <= 0 || _height <= 0) return false; 
    int ix = Math.round(relX * _image.width/_width); 
    int iy = Math.round(relY * _image.height/_height); 
    return (0 < _image.get(ix, iy)>>>24);
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float alphaPc ) { 
    if (_image==null) return; 
    alphaPc *= (_fill>>>24); 
    g.tint( _fill | 0xFF000000, Math.round(alphaPc) ); 
    int wscale = 1; 
    int hscale = 1; 
    float w = _width; 
    float h = _height; 
    if (_width < 0) { 
      w = -_width; 
      wscale = -1; 
      drawX = -drawX;
    } 
    if (_height < 0) { 
      h = -_height; 
      hscale = -1; 
      drawY = -drawY;
    } 
    g.pushMatrix(); 
    g.scale(wscale, hscale); 
    g.image(_image, drawX, drawY, w, h); 
    g.popMatrix();
  }
} 


public static class HPath extends HDrawable { 
  public static final int HANDLE_FILL = 0xFFFF0000; 
  public static final int HANDLE_STROKE = 0xFF202020; 
  public static final float HANDLE_STROKE_WEIGHT = 1; 
  public static final float HANDLE_SIZE = 6; 
  private ArrayList<HVertex> _vertices; 
  private int _mode; 
  private boolean _drawsHandles; 
  public HPath() { 
    this(PConstants.PATH);
  } 
  public HPath(int modeId) { 
    _mode = modeId; 
    _vertices = new ArrayList<HVertex>();
  } 
  public HPath createCopy() { 
    HPath copy = new HPath(_mode); 
    copy.copyPropertiesFrom(this); 
    copy._drawsHandles = _drawsHandles; 
    for (int i=0; i<numVertices(); ++i) { 
      copy._vertices.add(vertex(i).createCopy(copy));
    } 
    return copy;
  } 
  public HPath mode(int modeId) { 
    _mode = modeId; 
    return this;
  } 
  public int mode() { 
    return _mode;
  } 
  public HPath drawsHandles(boolean b) { 
    _drawsHandles = b; 
    return this;
  } 
  public boolean drawsHandles() { 
    return _drawsHandles;
  } 
  public int numVertices() { 
    return _vertices.size();
  } 
  public HVertex vertex(int index) { 
    return _vertices.get(index);
  } 
  public HPath vertex(float x, float y) { 
    _vertices.add(new HVertex(this).set(x, y)); 
    return this;
  } 
  public HPath vertex(float cx, float cy, float x, float y) { 
    _vertices.add(new HVertex(this).set(cx, cy, x, y)); 
    return this;
  } 
  public HPath vertex( float cx1, float cy1, float cx2, float cy2, float x, float y ) { 
    _vertices.add(new HVertex(this).set(cx1, cy1, cx2, cy2, x, y)); 
    return this;
  } 
  public HPath vertexUV(float u, float v) { 
    _vertices.add(new HVertex(this).setUV(u, v)); 
    return this;
  } 
  public HPath vertexUV(float cu, float cv, float u, float v) { 
    _vertices.add(new HVertex(this).setUV(cu, cv, u, v)); 
    return this;
  } 
  public HPath vertexUV( float cu1, float cv1, float cu2, float cv2, float u, float v ) { 
    _vertices.add(new HVertex(this).setUV(cu1, cv1, cu2, cv2, u, v)); 
    return this;
  } 
  public HPath adjust() { 
    int numv = numVertices(); 
    float[] minmax = new float[4]; 
    for (int i=0; i<numv; ++i) vertex(i).computeMinMax(minmax); 
    float offU = -minmax[0], offV = -minmax[1]; 
    float oldW = _width, oldH = _height; 
    anchorUV(offU, offV).scale(minmax[2]+offU, minmax[3]+offV); 
    for (int i=0; i<numv; ++i) vertex(i).adjust(offU, offV, oldW, oldH); 
    return this;
  } 
  public HPath endPath() { 
    return adjust();
  } 
  public HPath reset() { 
    size(DEFAULT_WIDTH, DEFAULT_HEIGHT).anchorUV(0, 0); 
    return clear();
  } 
  public HPath beginPath(int modeId) { 
    return reset().mode(modeId);
  } 
  public HPath beginPath() { 
    return reset();
  } 
  public HPath clear() { 
    _vertices.clear(); 
    return this;
  } 
  public HPath line(float x1, float y1, float x2, float y2) { 
    return beginPath(PConstants.PATH) .vertex(x1, y1).vertex(x2, y2).endPath();
  } 
  public HPath lineUV(float u1, float v1, float u2, float v2) { 
    return beginPath(PConstants.PATH) .vertexUV(u1, v1).vertexUV(u2, v2).endPath();
  } 
  public HPath triangle(int type, int direction) { 
    clear().mode(PConstants.POLYGON); 
    float ratio = 2; 
    switch(type) { 
    case HConstants.EQUILATERAL: 
      ratio = (float) Math.sin(PConstants.TWO_PI/6); 
      break; 
    case HConstants.RIGHT: 
      ratio = (float) Math.sin(PConstants.TWO_PI/8)/HConstants.SQRT2; 
      break;
    } 
    switch(direction) { 
    case HConstants.TOP: 
    case HConstants.CENTER_TOP: 
      vertexUV(.5f, 0).vertexUV(0, 1).vertexUV(1, 1); 
      if (ratio < 2) height(_width*ratio).proportional(true); 
      break; 
    case HConstants.BOTTOM: 
    case HConstants.CENTER_BOTTOM: 
      vertexUV(.5f, 1).vertexUV(1, 0).vertexUV(0, 0); 
      if (ratio < 2) height(_width*ratio).proportional(true); 
      break; 
    case HConstants.RIGHT: 
    case HConstants.CENTER_RIGHT: 
      vertexUV(1, .5f).vertexUV(0, 0).vertexUV(0, 1); 
      if (ratio < 2) width(_height*ratio).proportional(true); 
      break; 
    case HConstants.LEFT: 
    case HConstants.CENTER_LEFT: 
      vertexUV(0, .5f).vertexUV(1, 1).vertexUV(1, 0); 
      if (ratio < 2) width(_height*ratio).proportional(true); 
      break; 
    case HConstants.TOP_LEFT: 
      vertexUV(0, 0).vertexUV(0, 1).vertexUV(1, 0); 
      break; 
    case HConstants.TOP_RIGHT: 
      vertexUV(1, 0).vertexUV(0, 0).vertexUV(1, 1); 
      break; 
    case HConstants.BOTTOM_RIGHT: 
      vertexUV(1, 1).vertexUV(0, 1).vertexUV(1, 0); 
      break; 
    case HConstants.BOTTOM_LEFT: 
      vertexUV(0, 1).vertexUV(0, 0).vertexUV(1, 1); 
      break;
    } 
    return this;
  } 
  public HPath polygon(int numEdges) { 
    return polygonRad(numEdges, 0);
  } 
  public HPath polygon(int numEdges, float startDeg) { 
    return polygonRad(numEdges, startDeg*HConstants.D2R);
  } 
  public HPath polygonRad(int numEdges, float startRad) { 
    clear().mode(PConstants.POLYGON); 
    float inc = PConstants.TWO_PI / numEdges; 
    for (int i=0; i<numEdges; ++i) { 
      float rad = startRad + inc*i; 
      vertexUV( 0.5f + 0.5f*(float)Math.cos(rad), 0.5f + 0.5f*(float)Math.sin(rad));
    } 
    return this;
  } 
  public HPath star(int numEdges, float depth) { 
    return starRad(numEdges, depth, 0);
  } 
  public HPath star(int numEdges, float depth, float startDeg) { 
    return starRad(numEdges, depth, startDeg*HConstants.D2R);
  } 
  public HPath starRad(int numEdges, float depth, float startRad) { 
    clear().mode(PConstants.POLYGON); 
    float inc = PConstants.TWO_PI / numEdges; 
    float idepth2 = (1-depth) * 0.5f; 
    for (int i=0; i<numEdges; ++i) { 
      float rad = startRad + inc*i; 
      vertexUV( 0.5f + 0.5f*(float)Math.cos(rad), 0.5f + 0.5f*(float)Math.sin(rad)); 
      rad += inc/2; 
      vertexUV( 0.5f + idepth2*(float)Math.cos(rad), 0.5f + idepth2*(float)Math.sin(rad));
    } 
    return this;
  } 
  public boolean containsRel(float relX, float relY) { 
    int numv = numVertices(); 
    if (numv <= 0) return false; 
    if (_width == 0) return (relX == 0) && (0<relY && relY<_height); 
    if (_height == 0) return (relY == 0) && (0<relX && relX<_width); 
    if ( !super.containsRel(relX, relY) ) return false; 
    boolean openPath = false; 
    switch(_mode) { 
    case PConstants.POINTS: 
      for (int i=0; i<numv; ++i) { 
        HVertex curr = vertex(i); 
        if (curr.u()==relX/_width && curr.v()==relY/_height) return true;
      } 
      return false; 
    case PConstants.PATH: 
      openPath = true; 
      if (HColors.isTransparent(_fill)) { 
        HVertex prev = vertex(openPath? 0 : numv-1); 
        for (int i=(openPath? 1 : 0); 
        i<numv; 
        ++i) { 
          HVertex curr = vertex(i); 
          if (curr.inLine(prev, relX, relY)) return true; 
          prev = curr; 
          if (openPath) openPath = false;
        } 
        return false;
      } 
    default: 
      float u = relX / _width; 
      float v = relY / _height; 
      boolean isIn = false; 
      HVertex prev = vertex(numv-1); 
      HVertex pprev = vertex(numv>1? numv-2 : 0); 
      for (int i=0; i<numv; ++i) { 
        HVertex curr = vertex(i); 
        if (curr.intersectTest(pprev, prev, u, v, openPath)) isIn = !isIn; 
        pprev = prev; 
        prev = curr; 
        if (openPath) openPath = false;
      } 
      return isIn;
    }
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float alphaPc ) { 
    int numv = numVertices(); 
    if (numv <= 0) return; 
    applyStyle(g, alphaPc); 
    boolean drawsLines = (_mode != PConstants.POINTS); 
    boolean isPolygon = (_mode==PConstants.POLYGON && numv>2); 
    boolean isSimple = true; 
    if (drawsLines) g.beginShape(); 
    else g.beginShape(PConstants.POINTS); 
    int itrs = (isPolygon)? numv+1 : numv; 
    for (int i=0; i<itrs; ++i) { 
      HVertex v = vertex(i<numv? i : 0); 
      v.draw(g, drawX, drawY, isSimple); 
      if (isSimple && drawsLines) isSimple = false;
    } 
    if (isPolygon) g.endShape(PConstants.CLOSE); 
    else g.endShape(); 
    if (_drawsHandles && drawsLines) { 
      HVertex prev = vertex(isPolygon? numv-1 : 0); 
      for (int i=(isPolygon? 0 : 1); 
      i<numv; 
      ++i) { 
        HVertex curr = vertex(i); 
        curr.drawHandles(g, prev, drawX, drawY); 
        prev = curr;
      }
    }
  }
} 


public static class HRect extends HDrawable { 
  private float _tl, _tr, _bl, _br; 
  public HRect() {
  } 
  public HRect(float s) { 
    size(s);
  } 
  public HRect(float w, float h) { 
    size(w, h);
  } 
  public HRect(float w, float h, float roundingRadius) { 
    size(w, h); 
    rounding(roundingRadius);
  } 
  public HRect createCopy() { 
    HRect copy = new HRect(); 
    copy._tl = _tl; 
    copy._tr = _tr; 
    copy._bl = _bl; 
    copy._br = _br; 
    copy.copyPropertiesFrom(this); 
    return copy;
  } 
  public HRect rounding(float radius) { 
    _tl = _tr = _bl = _br = radius; 
    return this;
  } 
  public HRect rounding( float topleft, float topright, float bottomright, float bottomleft ) { 
    _tl = topleft; 
    _tr = topright; 
    _br = bottomright; 
    _bl = bottomleft; 
    return this;
  } 
  public float rounding() { 
    return roundingTL();
  } 
  public HRect roundingTL(float radius) { 
    _tl = radius; 
    return this;
  } 
  public float roundingTL() { 
    return _tl;
  } 
  public HRect roundingTR(float radius) { 
    _tr = radius; 
    return this;
  } 
  public float roundingTR() { 
    return _tr;
  } 
  public HRect roundingBR(float radius) { 
    _br = radius; 
    return this;
  } 
  public float roundingBR() { 
    return _br;
  } 
  public HRect roundingBL(float radius) { 
    _bl = radius; 
    return this;
  } 
  public float roundingBL() { 
    return _bl;
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float alphaPc ) { 
    applyStyle(g, alphaPc); 
    g.rect(drawX, drawY, _width, _height, _tl, _tr, _br, _bl);
  }
} 


public static class HShape extends HDrawable { 
  private PShape _shape; 
  private int[] _randomFills, _randomStrokes; 
  public HShape() { 
    shape(null);
  } 
  public HShape(Object shapeArg) { 
    shape(shapeArg);
  } 
  public HShape createCopy() { 
    HShape copy = new HShape(_shape); 
    copy.copyPropertiesFrom(this); 
    return copy;
  } 
  public HShape resetSize() { 
    if (_shape == null) { 
      size(0, 0);
    } 
    else { 
      size(_shape.width, _shape.height);
    } 
    return this;
  } 
  public HShape shape(Object shapeArg) { 
    if (shapeArg instanceof PShape) { 
      _shape = (PShape) shapeArg;
    } 
    else if (shapeArg instanceof String) { 
      _shape = H.app().loadShape((String) shapeArg);
    } 
    else if (shapeArg instanceof HShape) { 
      _shape = ((HShape) shapeArg)._shape;
    } 
    else if (shapeArg == null) { 
      _shape = null;
    } 
    return resetSize();
  } 
  public PShape shape() { 
    return _shape;
  } 
  public HShape enableStyle(boolean b) { 
    if (b) _shape.enableStyle(); 
    else _shape.disableStyle(); 
    return this;
  } 
  public HShape randomColors(HColorPool colors) { 
    int numChildren = _shape.getChildCount(); 
    boolean isFill = colors.appliesFill(); 
    boolean isStroke = colors.appliesStroke(); 
    if (isFill) { 
      if (_randomFills==null || _randomFills.length<numChildren) _randomFills = new int[numChildren];
    } 
    else { 
      _randomFills = null;
    } 
    if (isStroke) { 
      if (_randomStrokes==null || _randomStrokes.length<numChildren) _randomStrokes = new int[numChildren];
    } 
    else { 
      _randomStrokes = null;
    } 
    for (int i=0; i<numChildren; ++i) { 
      if (isFill) _randomFills[i] = colors.getColor(); 
      if (isStroke) _randomStrokes[i] = colors.getColor();
    } 
    _shape.disableStyle(); 
    return this;
  } 
  public HShape resetRandomColors() { 
    _shape.enableStyle(); 
    _randomFills = null; 
    _randomStrokes = null; 
    return this;
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float alphaPc ) { 
    if (_shape == null) return; 
    int wscale = 1; 
    int hscale = 1; 
    float w = _width; 
    float h = _height; 
    if (_width < 0) { 
      w = -_width; 
      wscale = -1; 
      drawX = -drawX;
    } 
    if (_height < 0) { 
      h = -_height; 
      hscale = -1; 
      drawY = - drawY;
    } 
    applyStyle(g, alphaPc); 
    g.pushMatrix(); 
    g.scale(wscale, hscale); 
    if (_randomFills==null && _randomStrokes==null) { 
      g.shape(_shape, drawX, drawY, w, h);
    } 
    else for (int i=0; i<_shape.getChildCount(); ++i) { 
      PShape childShape = _shape.getChild(i); 
      childShape.width = _shape.width; 
      childShape.height = _shape.height; 
      if (_randomFills != null) g.fill(_randomFills[i]); 
      if (_randomStrokes != null) g.stroke(_randomStrokes[i]); 
      g.shape(childShape, drawX, drawY, w, h);
    } 
    g.popMatrix();
  }
} 


public static class HSphere extends HDrawable3D { 
  public HSphere() {
  } 
  public HSphere(float radius) { 
    radius(radius);
  } 
  public HSphere(float radiusw, float radiush, float radiusd) { 
    radius(radiusw, radiush, radiusd);
  } 
  public HSphere radius(float f) { 
    return (HSphere) size(f*2);
  } 
  public HSphere radius(float radiusw, float radiush, float radiusd) { 
    return (HSphere) size(radiusw*2, radiush*2, radiusd*2);
  } 
  protected void onResize(float oldW, float oldH, float newW, float newH) { 
    _height = _depth = _width; 
    super.onResize(oldW, oldH, newW, newH);
  } 
  public HSphere createCopy() { 
    HSphere copy = new HSphere(); 
    copy.copyPropertiesFrom(this); 
    copy._depth = _depth; 
    copy._anchorW = _anchorW; 
    return copy;
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float currAlphaPc ) { 
    applyStyle(g, currAlphaPc); 
    g.pushMatrix(); 
    g.translate(drawX+_width/2, drawY+_height/2, -anchorZ()+_depth/2); 
    g.scale(_width, _height, _depth); 
    g.sphere(1); 
    g.popMatrix();
  }
} 


public static class HText extends HDrawable { 
  private PFont _font; 
  private String _text; 
  private float _descent; 
  public HText() { 
    this(null, 16);
  } 
  public HText(String textString) { 
    this(textString, 16, null);
  } 
  public HText(String textString, float size) { 
    this(textString, size, null);
  } 
  public HText(String textString, float size, Object fontArg) { 
    _text = textString; 
    _height = size; 
    font(fontArg); 
    height(size); 
    _fill = HConstants.BLACK; 
    _stroke = HConstants.CLEAR;
  } 
  public HText createCopy() { 
    HText copy = new HText(_text, _height, _font); 
    copy.copyPropertiesFrom(this); 
    copy.adjustMetrics(); 
    return copy;
  } 
  public HText text(String txt) { 
    _text = txt; 
    adjustMetrics(); 
    return this;
  } 
  public String text() { 
    return _text;
  } 
  public HText font(Object arg) { 
    PApplet app = H.app(); 
    if (arg instanceof PFont) { 
      _font = (PFont) arg;
    } 
    else if (arg instanceof String) { 
      String str = (String) arg; 
      _font = (str.indexOf(".vlw", str.length()-4) > 0)? app.loadFont(str) : app.createFont(str, 64);
    } 
    else if (arg instanceof HText) { 
      _font = ((HText) arg)._font;
    } 
    else if (arg == null) { 
      _font = app.createFont("SansSerif", 64);
    } 
    adjustMetrics(); 
    return this;
  } 
  public PFont font() { 
    return _font;
  } 
  public HText fontSize(float f) { 
    return height(f);
  } 
  public float fontSize() { 
    return _height;
  } 
  private void adjustMetrics() { 
    PApplet app = H.app(); 
    app.pushStyle(); 
    app.textFont(_font, (_height < 0)? -_height : _height); 
    _descent = app.textDescent(); 
    _width = (_text==null)? 0 : (_width<0)? -app.textWidth(_text) : app.textWidth(_text); 
    app.popStyle();
  } 
  public HText width(float w) { 
    if (w<0 == _width>0) _width = -_width; 
    return this;
  } 
  public HText height(float h) { 
    _height = h; 
    adjustMetrics(); 
    return this;
  } 
  public boolean containsRel(float relX, float relY) { 
    if (_text == null || _height == 0) return false; 
    int numChars = _text.length(); 
    float ratio = 64 / _height; 
    float xoff = 0; 
    float yoff = (_height - _descent) * ratio; 
    relX *= ratio; 
    relY *= ratio; 
    for (int i=0; i<numChars; ++i) { 
      char c = _text.charAt(i); 
      PFont.Glyph g = _font.getGlyph(c); 
      int pxx = Math.round(relX - xoff); 
      int pxy = Math.round(relY - yoff) + g.topExtent; 
      if (g.image.get(pxx, pxy)>>>24 > 0) return true; 
      xoff += g.setWidth;
    } 
    return false;
  } 
  public void draw( PGraphics g, boolean usesZ, float drawX, float drawY, float alphaPc ) { 
    if (_text == null) return; 
    applyStyle(g, alphaPc); 
    int wscale = 1; 
    int hscale = 1; 
    float h = _height; 
    if (_width < 0) { 
      wscale = -1; 
      drawX = -drawX;
    } 
    if (_height < 0) { 
      h = -_height; 
      hscale = -1; 
      drawY = -drawY;
    } 
    g.pushMatrix(); 
    g.scale(wscale, hscale); 
    g.textFont(_font, h); 
    g.text(_text, drawX, drawY+h-_descent); 
    g.popMatrix();
  }
} 
public static class HGridLayout implements HLayout { 
  private int _currentIndex, _numCols; 
  private float _startX, _startY, _xSpace, _ySpace; 
  public HGridLayout() { 
    _xSpace = _ySpace = _numCols = 16;
  } 
  public HGridLayout(int numOfColumns) { 
    this(); 
    _numCols = numOfColumns;
  } 
  public HGridLayout currentIndex(int i) { 
    _currentIndex = i; 
    return this;
  } 
  public int currentIndex() { 
    return _currentIndex;
  } 
  public HGridLayout resetIndex() { 
    _currentIndex = 0; 
    return this;
  } 
  public HGridLayout cols(int numOfColumns) { 
    _numCols = numOfColumns; 
    return this;
  } 
  public int cols() { 
    return _numCols;
  } 
  public PVector startLoc() { 
    return new PVector(_startX, _startY);
  } 
  public HGridLayout startLoc(float x, float y) { 
    _startX = x; 
    _startY = y; 
    return this;
  } 
  public float startX() { 
    return _startX;
  } 
  public HGridLayout startX(float x) { 
    _startX = x; 
    return this;
  } 
  public float startY() { 
    return _startY;
  } 
  public HGridLayout startY(float y) { 
    _startY = y; 
    return this;
  } 
  public PVector spacing() { 
    return new PVector(_xSpace, _ySpace);
  } 
  public HGridLayout spacing(float xSpacing, float ySpacing) { 
    _xSpace = xSpacing; 
    _ySpace = ySpacing; 
    return this;
  } 
  public float spacingX() { 
    return _xSpace;
  } 
  public HGridLayout spacingX(float xSpacing) { 
    _xSpace = xSpacing; 
    return this;
  } 
  public float spacingY() { 
    return _ySpace;
  } 
  public HGridLayout spacingY(float ySpacing) { 
    _ySpace = ySpacing; 
    return this;
  } 
  public PVector getNextPoint() { 
    int row = (int) Math.floor(_currentIndex / _numCols); 
    int col = _currentIndex % _numCols; 
    ++_currentIndex; 
    return new PVector(col*_xSpace + _startX, row*_ySpace + _startY);
  } 
  public void applyTo(HDrawable target) { 
    target.loc(getNextPoint());
  }
} 
public static class HShapeLayout implements HLayout { 
  private HDrawable _target; 
  private float[] _bounds; 
  private int _iterationLimit; 
  public HShapeLayout() { 
    _iterationLimit = 1024; 
    _bounds = new float[4];
  } 
  public HShapeLayout iterationLimit(int i) { 
    _iterationLimit = i; 
    return this;
  } 
  public int iterationLimit() { 
    return _iterationLimit;
  } 
  public HShapeLayout target(HDrawable d) { 
    _target = d; 
    if (_target != null) _target.bounds(_bounds); 
    return this;
  } 
  public HDrawable target() { 
    return _target;
  } 
  public void applyTo(HDrawable target) { 
    PVector pt = getNextPoint(); 
    if (pt != null) target.loc(pt);
  } 
  public PVector getNextPoint() { 
    if (_target == null) return null; 
    float x1 = _bounds[0]; 
    float y1 = _bounds[1]; 
    float x2 = _bounds[0] + _bounds[2]; 
    float y2 = _bounds[1] + _bounds[3]; 
    for (int i=0; i<_iterationLimit; ++i) { 
      float x = H.app().random(x1, x2); 
      float y = H.app().random(y1, y2); 
      if (_target.contains(x, y)) return new PVector(x, y);
    } 
    return null;
  }
} 
public static class HDrawablePool implements Iterable<HDrawable> { 
  private HLinkedHashSet<HDrawable> _activeSet, _inactiveSet; 
  private ArrayList<HDrawable> _prototypes; 
  private HCallback _onCreate, _onRequest, _onRelease; 
  private HLayout _layout; 
  private HColorist _colorist; 
  private HDrawable _autoParent; 
  private int _max; 
  public HDrawablePool() { 
    this(64);
  } 
  public HDrawablePool(int maximumDrawables) { 
    _max = maximumDrawables; 
    _activeSet = new HLinkedHashSet<HDrawable>(); 
    _inactiveSet = new HLinkedHashSet<HDrawable>(); 
    _prototypes = new ArrayList<HDrawable>(); 
    _onCreate = _onRequest = _onRelease = HConstants.NOP;
  } 
  public int max() { 
    return _max;
  } 
  public HDrawablePool max(int m) { 
    _max = m; 
    return this;
  } 
  public int numActive() { 
    return _activeSet.size();
  } 
  public int numInactive() { 
    return _inactiveSet.size();
  } 
  public int currentIndex() { 
    return _activeSet.size() - 1;
  } 
  public HLayout layout() { 
    return _layout;
  } 
  public HDrawablePool layout(HLayout newLayout) { 
    _layout = newLayout; 
    return this;
  } 
  public HColorist colorist() { 
    return _colorist;
  } 
  public HDrawablePool colorist(HColorist newColorist) { 
    _colorist = newColorist; 
    return this;
  } 
  public HDrawablePool onCreate(HCallback callback) { 
    _onCreate = (callback==null)? HConstants.NOP : callback; 
    return this;
  } 
  public HCallback onCreate() { 
    return _onCreate;
  } 
  public HDrawablePool onRequest(HCallback callback) { 
    _onRequest = (callback==null)? HConstants.NOP : callback; 
    return this;
  } 
  public HCallback onRequest() { 
    return _onRequest;
  } 
  public HDrawablePool onRelease(HCallback callback) { 
    _onRelease = (callback==null)? HConstants.NOP : callback; 
    return this;
  } 
  public HCallback onRelease() { 
    return _onRelease;
  } 
  public HDrawablePool autoParent(HDrawable parent) { 
    _autoParent = parent; 
    return this;
  } 
  public HDrawablePool autoAddToStage() { 
    _autoParent = H.stage(); 
    return this;
  } 
  public HDrawable autoParent() { 
    return _autoParent;
  } 
  public boolean isFull() { 
    return count() >= _max;
  } 
  public int count() { 
    return _activeSet.size() + _inactiveSet.size();
  } 
  public HDrawablePool destroy() { 
    _activeSet.removeAll(); 
    _inactiveSet.removeAll(); 
    _prototypes.clear(); 
    _onCreate = _onRequest = _onRelease = HConstants.NOP; 
    _layout = null; 
    _autoParent = null; 
    _max = 0; 
    return this;
  } 
  public HDrawablePool add(HDrawable prototype, int frequency) { 
    if (prototype == null) { 
      HWarnings.warn("Null Prototype", "HDrawablePool.add()", HWarnings.NULL_ARGUMENT);
    } 
    else { 
      _prototypes.add(prototype); 
      while (frequency-- > 0) _prototypes.add(prototype);
    } 
    return this;
  } 
  public HDrawablePool add(HDrawable prototype) { 
    return add(prototype, 1);
  } 
  public HDrawable request() { 
    if (_prototypes.size() <= 0) { 
      HWarnings.warn("No Prototype", "HDrawablePool.request()", HWarnings.NO_PROTOTYPE); 
      return null;
    } 
    HDrawable drawable; 
    boolean onCreateFlag = false; 
    if (_inactiveSet.size() > 0) { 
      drawable = _inactiveSet.pull();
    } 
    else if (count() < _max) { 
      drawable = createRandomDrawable(); 
      onCreateFlag = true;
    } 
    else return null; 
    _activeSet.add(drawable); 
    if (_autoParent != null) _autoParent.add(drawable); 
    if (_layout != null) _layout.applyTo(drawable); 
    if (_colorist != null) _colorist.applyColor(drawable); 
    if (onCreateFlag) _onCreate.run(drawable); 
    _onRequest.run(drawable); 
    return drawable;
  } 
  public HDrawablePool requestAll() { 
    if (_prototypes.size() <= 0) { 
      HWarnings.warn("No Prototype", "HDrawablePool.requestAll()", HWarnings.NO_PROTOTYPE);
    } 
    else { 
      while (count () < _max) request();
    } 
    return this;
  } 
  public boolean release(HDrawable d) { 
    if (_activeSet.remove(d)) { 
      _inactiveSet.add(d); 
      if (_autoParent != null) _autoParent.remove(d); 
      _onRelease.run(d); 
      return true;
    } 
    return false;
  } 
  public HLinkedHashSet<HDrawable> activeSet() { 
    return _activeSet;
  } 
  public HLinkedHashSet<HDrawable> inactiveSet() { 
    return _inactiveSet;
  } 
  private HDrawable createRandomDrawable() { 
    int index = HMath.randomInt(_prototypes.size()); 
    return _prototypes.get(index).createCopy();
  } 
  public Iterator<HDrawable> iterator() { 
    return _activeSet.iterator();
  }
} 
public static class HVertex implements HLocatable { 
  public static final float LINE_TOLERANCE = 1.5f; 
  private HPath _path; 
  private byte _numControlPts; 
  private float _u, _v, _cu1, _cv1, _cu2, _cv2; 
  public HVertex(HPath parentPath) { 
    _path = parentPath;
  } 
  public HVertex createCopy(HPath newParentPath) { 
    HVertex copy = new HVertex(newParentPath); 
    copy._numControlPts = _numControlPts; 
    copy._u = _u; 
    copy._v = _v; 
    copy._cu1 = _cu1; 
    copy._cv1 = _cv1; 
    copy._cu2 = _cu2; 
    copy._cv2 = _cv2; 
    return copy;
  } 
  public HPath path() { 
    return _path;
  } 
  public HVertex numControlPts(byte b) { 
    _numControlPts = b; 
    return this;
  } 
  public byte numControlPts() { 
    return _numControlPts;
  } 
  public boolean isLine() { 
    return (_numControlPts <= 0);
  } 
  public boolean isCurved() { 
    return (_numControlPts > 0);
  } 
  public boolean isQuadratic() { 
    return (_numControlPts == 1);
  } 
  public boolean isCubic() { 
    return (_numControlPts >= 2);
  } 
  public HVertex set(float x, float y) { 
    return setUV( _path.x2u(x), _path.y2v(y));
  } 
  public HVertex set(float cx, float cy, float x, float y) { 
    return setUV( _path.x2u(cx), _path.y2v(cy), _path.x2u(x), _path.y2v(y));
  } 
  public HVertex set( float cx1, float cy1, float cx2, float cy2, float x, float y ) { 
    return setUV( _path.x2u(cx1), _path.y2v(cy1), _path.x2u(cx2), _path.y2v(cy2), _path.x2u(x), _path.y2v(y));
  } 
  public HVertex setUV(float u, float v) { 
    _numControlPts = 0; 
    _u = u; 
    _v = v; 
    return this;
  } 
  public HVertex setUV(float cu, float cv, float u, float v) { 
    _numControlPts = 1; 
    _u = u; 
    _v = v; 
    _cu1 = cu; 
    _cv1 = cv; 
    return this;
  } 
  public HVertex setUV( float cu1, float cv1, float cu2, float cv2, float u, float v ) { 
    _numControlPts = 2; 
    _u = u; 
    _v = v; 
    _cu1 = cu1; 
    _cv1 = cv1; 
    _cu2 = cu2; 
    _cv2 = cv2; 
    return this;
  } 
  public HVertex x(float f) { 
    return u(_path.x2u(f));
  } 
  public float x() { 
    return _path.u2x(_u);
  } 
  public HVertex y(float f) { 
    return v(_path.y2v(f));
  } 
  public float y() { 
    return _path.v2y(_v);
  } 
  public HVertex z(float f) { 
    return this;
  } 
  public float z() { 
    return 0;
  } 
  public HVertex u(float f) { 
    _u = f; 
    return this;
  } 
  public float u() { 
    return _u;
  } 
  public HVertex v(float f) { 
    _v = f; 
    return this;
  } 
  public float v() { 
    return _v;
  } 
  public HVertex cx(float f) { 
    return cx1(f);
  } 
  public float cx() { 
    return cx1();
  } 
  public HVertex cy(float f) { 
    return cy1(f);
  } 
  public float cy() { 
    return cy1();
  } 
  public HVertex cu(float f) { 
    return cu1(f);
  } 
  public float cu() { 
    return cu1();
  } 
  public HVertex cv(float f) { 
    return cv1(f);
  } 
  public float cv() { 
    return cv1();
  } 
  public HVertex cx1(float f) { 
    return cu1(_path.x2u(f));
  } 
  public float cx1() { 
    return _path.u2x(_cu1);
  } 
  public HVertex cy1(float f) { 
    return cv1(_path.y2v(f));
  } 
  public float cy1() { 
    return _path.v2y(_cv1);
  } 
  public HVertex cu1(float f) { 
    _cu1 = f; 
    return this;
  } 
  public float cu1() { 
    return _cu1;
  } 
  public HVertex cv1(float f) { 
    _cv1 = f; 
    return this;
  } 
  public float cv1() { 
    return _cv1;
  } 
  public HVertex cx2(float f) { 
    return cu2(_path.x2u(f));
  } 
  public float cx2() { 
    return _path.u2x(_cu2);
  } 
  public HVertex cy2(float f) { 
    return cv2(_path.y2v(f));
  } 
  public float cy2() { 
    return _path.v2y(_cv2);
  } 
  public HVertex cu2(float f) { 
    _cu2 = f; 
    return this;
  } 
  public float cu2() { 
    return _cu2;
  } 
  public HVertex cv2(float f) { 
    _cv2 = f; 
    return this;
  } 
  public float cv2() { 
    return _cv2;
  } 
  public void computeMinMax(float[] minmax) { 
    if (_u < minmax[0]) minmax[0] = _u; 
    else if (_u > minmax[2]) minmax[2] = _u; 
    if (_v < minmax[1]) minmax[1] = _v; 
    else if (_v > minmax[3]) minmax[3] = _v; 
    switch(_numControlPts) { 
    case 2: 
      if (_cu2 < minmax[0]) minmax[0] = _cu2; 
      else if (_cu2 > minmax[2]) minmax[2] = _cu2; 
      if (_cv2 < minmax[1]) minmax[1] = _cv2; 
      else if (_cv2 > minmax[3]) minmax[3] = _cv2; 
    case 1: 
      if (_cu1 < minmax[0]) minmax[0] = _cu1; 
      else if (_cu1 > minmax[2]) minmax[2] = _cu1; 
      if (_cv1 < minmax[1]) minmax[1] = _cv1; 
      else if (_cv1 > minmax[3]) minmax[3] = _cv1; 
      break; 
    default: 
      break;
    }
  } 
  public void adjust(float offsetU, float offsetV, float oldW, float oldH) { 
    x( oldW*(_u += offsetU) ).y( oldH*(_v += offsetV) ); 
    switch(_numControlPts) { 
    case 2: 
      cx2( oldW*(_cu2 += offsetU) ).cy2( oldH*(_cv2 += offsetV) ); 
    case 1: 
      cx1( oldW*(_cu1 += offsetU) ).cy1( oldH*(_cv1 += offsetV) ); 
      break; 
    default: 
      break;
    }
  } 
  private float dv(float pv, float t) { 
    switch(_numControlPts) { 
    case 1: 
      return HMath.bezierTangent(pv, _cv1, _v, t); 
    case 2: 
      return HMath.bezierTangent(pv, _cv2, _cv2, _v, t); 
    default: 
      return _v - pv;
    }
  } 
  public boolean intersectTest( HVertex pprev, HVertex prev, float tu, float tv, boolean openPath ) { 
    float u1 = prev._u; 
    float v1 = prev._v; 
    float u2 = _u; 
    float v2 = _v; 
    if (isLine() || openPath) { 
      return ((v1<=tv && tv<v2) || (v2<=tv && tv<v1)) && tu < (u1 + (u2-u1)*(tv-v1)/(v2-v1));
    } 
    else if (isQuadratic()) { 
      boolean b = false; 
      float[] params = new float[2]; 
      int numParams = HMath.bezierParam(v1, _cv1, v2, tv, params); 
      for (int i=0; i<numParams; ++i) { 
        float t = params[i]; 
        if (0<t && t<1 && tu<HMath.bezierPoint(u1, _cu1, u2, t)) { 
          if (HMath.bezierTangent(v1, _cv1, v2, t) == 0) continue; 
          b = !b;
        } 
        else if (t==0 && tu<u1) { 
          float ptanv = prev.dv(pprev._v, 1); 
          if (ptanv==0) ptanv = prev.dv(pprev._v, 0.9375f); 
          float ntanv = HMath.bezierTangent(v1, _cv1, v2, 0); 
          if (ntanv==0) ntanv=HMath.bezierTangent(v1, _cv1, v2, 0.0625f); 
          if (ptanv<0 == ntanv<0) b = !b;
        }
      } 
      return b;
    } 
    else { 
      boolean b = false; 
      float[] params = new float[3]; 
      int numParams = HMath.bezierParam(v1, _cv1, _cv2, v2, tv, params); 
      for (int i=0; i<numParams; ++i) { 
        float t = params[i]; 
        if (0<t && t<1 && tu<HMath.bezierPoint(u1, _cu1, _cu2, u2, t)) { 
          if (HMath.bezierTangent(v1, _cv1, _cv2, _v, t) == 0) { 
            float ptanv = HMath.bezierTangent( v1, _cv1, _cv2, v2, Math.max(t-0.0625f, 0)); 
            float ntanv = HMath.bezierTangent( v1, _cv1, _cv2, v2, Math.min(t+.0625f, 1)); 
            if (ptanv<0 != ntanv<0) continue;
          } 
          b = !b;
        } 
        else if (t==0 && tu<u1) { 
          float ptanv = prev.dv(pprev._v, 1); 
          if (ptanv==0) ptanv = prev.dv(pprev._v, 0.9375f); 
          float ntanv = HMath.bezierTangent(v1, _cv1, _cv2, 0); 
          if (ntanv==0) ntanv = HMath.bezierTangent( v1, _cv1, _cv2, v2, 0.0625f); 
          if (ptanv<0 == ntanv<0) b = !b;
        }
      } 
      return b;
    }
  } 
  public boolean inLine(HVertex prev, float relX, float relY) { 
    float x1 = prev.x(); 
    float y1 = prev.y(); 
    float x2 = x(); 
    float y2 = y(); 
    if (isLine()) { 
      float diffv = y2-y1; 
      if (diffv == 0) { 
        return HMath.isEqual(relY, y1, LINE_TOLERANCE) && ( (x1<=relX && relX<=x2)||(x2<=relX && relX<=x1) );
      } 
      float t = (relY-y1) / diffv; 
      return (0<=t && t<=1) && HMath.isEqual(relX, x1+(x2-x1)*t, LINE_TOLERANCE);
    } 
    else if (isQuadratic()) { 
      float[] params = new float[2]; 
      int numParams = HMath.bezierParam(y1, cy1(), y2, relY, params); 
      for (int i=0; i<numParams; ++i) { 
        float t = params[i]; 
        if (0<=t && t<=1) { 
          float bzval = HMath.bezierPoint(x1, cx1(), x2, t); 
          if (HMath.isEqual(relX, bzval, LINE_TOLERANCE)) return true;
        }
      } 
      return false;
    } 
    else { 
      float[] params = new float[3]; 
      int numParams = HMath.bezierParam(y1, cy1(), cy2(), y2, relY, params); 
      for (int i=0; i<numParams; ++i) { 
        float t = params[i]; 
        if (0<=t && t<=1) { 
          float bzval = HMath.bezierPoint(x1, cx1(), cx2(), x2, t); 
          if (HMath.isEqual(relX, bzval, LINE_TOLERANCE)) return true;
        }
      } 
      return false;
    }
  } 
  public void draw(PGraphics g, float drawX, float drawY, boolean isSimple) { 
    float drX = drawX + x(); 
    float drY = drawY + y(); 
    if (isLine() || isSimple) { 
      g.vertex(drX, drY);
    } 
    else if (isQuadratic()) { 
      float drCX = drawX + cx1(); 
      float drCY = drawY + cy1(); 
      g.quadraticVertex(drCX, drCY, drX, drY);
    } 
    else { 
      float drCX1 = drawX + cx1(); 
      float drCY1 = drawY + cy1(); 
      float drCX2 = drawX + cx2(); 
      float drCY2 = drawY + cy2(); 
      g.bezierVertex(drCX1, drCY1, drCX2, drCY2, drX, drY);
    }
  } 
  public void drawHandles(PGraphics g, HVertex prev, float drawX, float drawY) { 
    if (isLine()) return; 
    float x1 = drawX + prev.x(); 
    float y1 = drawY + prev.y(); 
    float x2 = drawX + x(); 
    float y2 = drawY + y(); 
    g.fill(HPath.HANDLE_FILL); 
    g.stroke(HPath.HANDLE_STROKE); 
    g.strokeWeight(HPath.HANDLE_STROKE_WEIGHT); 
    if (isQuadratic()) { 
      float drCX = drawX + cx1(); 
      float drCY = drawY + cy1(); 
      g.line(x1, y1, drCX, drCY); 
      g.line(x2, y2, drCX, drCY); 
      g.ellipse(drCX, drCY, HPath.HANDLE_SIZE, HPath.HANDLE_SIZE); 
      g.fill(HPath.HANDLE_STROKE); 
      g.ellipse(x1, y1, HPath.HANDLE_SIZE/2, HPath.HANDLE_SIZE/2); 
      g.ellipse(x2, y2, HPath.HANDLE_SIZE/2, HPath.HANDLE_SIZE/2);
    } 
    else { 
      float drCX1 = drawX + cx1(); 
      float drCY1 = drawY + cy1(); 
      float drCX2 = drawX + cx2(); 
      float drCY2 = drawY + cy2(); 
      g.line(x1, y1, drCX1, drCY1); 
      g.line(x2, y2, drCX2, drCY2); 
      g.line(drCX1, drCY1, drCX2, drCY2); 
      g.ellipse(drCX1, drCY1, HPath.HANDLE_SIZE, HPath.HANDLE_SIZE); 
      g.ellipse(drCX2, drCY2, HPath.HANDLE_SIZE, HPath.HANDLE_SIZE); 
      g.fill(HPath.HANDLE_STROKE); 
      g.ellipse(x1, y1, HPath.HANDLE_SIZE/2, HPath.HANDLE_SIZE/2); 
      g.ellipse(x2, y2, HPath.HANDLE_SIZE/2, HPath.HANDLE_SIZE/2);
    }
  }
}
import java.util.*;

