/*
* Copyright (C) 2009 Quadduc <quadduc@gmail.com>
*
* This file is part of LateralGM.
* LateralGM is free software and comes with ABSOLUTELY NO WARRANTY.
* See LICENSE for details.
*/
package org.lateralgm.util;
import java.awt.Rectangle;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
public class BinPlane
{
public final int binShift;
private final ConcurrentHashMap<Integer,WeakReference<Bin>> bins;
public BinPlane(int s, int w, int h)
{
binShift = 32 - Integer.numberOfLeadingZeros(s - 1);
int c = (1 + (w - 1 >> binShift)) * (1 + (h - 1 >> binShift)) * 2;
bins = new ConcurrentHashMap<Integer,WeakReference<Bin>>(c,0.5f,2);
}
public static enum Edge
{
LEFT(null)
{
public int compareBin(int i0, int i1)
{
return Integer.valueOf(i1 << 16 >> 16).compareTo(i0 << 16 >> 16);
}
public int getValue(int left, int right, int top, int bottom)
{
return -left;
}
},
RIGHT(LEFT),TOP(null)
{
public int compareBin(int i0, int i1)
{
return Integer.valueOf(i1 >> 16).compareTo(i0 >> 16);
}
public int getValue(int left, int right, int top, int bottom)
{
return -top;
}
},
BOTTOM(TOP);
private Edge opposite;
private Edge(Edge o)
{
opposite = o;
if (o != null) o.opposite = this;
}
public int compareBin(int i0, int i1)
{
return opposite.compareBin(i1,i0);
}
public int getValue(int left, int right, int top, int bottom)
{
return opposite.getValue(-right,-left,-bottom,-top);
}
public int compareBounds(Rectangle b0, Rectangle b1)
{
int v0 = getValue(b0.x,b0.x + b0.width,b0.y,b0.y + b0.height);
int v1 = getValue(b1.x,b1.x + b1.width,b1.y,b1.y + b1.height);
return v0 > v1 ? 1 : v0 < v1 ? -1 : 0;
}
}
public Bin[] getEdgeBins(Edge edge)
{
ArrayList<Bin> l = new ArrayList<Bin>();
int s = 0;
for (Entry<Integer,WeakReference<Bin>> e : bins.entrySet())
{
Bin b = e.getValue().get();
if (b == null || b.candidates.size() == 0) continue;
if (s == 0)
{
s = 1;
l.add(b);
continue;
}
switch (edge.compareBin(b.index,l.get(s - 1).index))
{
case 0:
if (l.size() > s++)
l.set(s - 1,b);
else
l.add(b);
break;
case 1:
s = 1;
if (l.size() > 0)
l.set(0,b);
else
l.add(b);
break;
default:
continue;
}
}
return l.subList(0,s).toArray(new Bin[s]);
}
public Candidate getEdgeCandidate(Edge edge)
{
Candidate ec = null;
for (Bin b : getEdgeBins(edge))
for (Candidate c : b.candidates)
if (ec == null || edge.compareBounds(c.bounds,ec.bounds) > 0) ec = c;
return ec;
}
public Iterator<Candidate> getBin(final int bx, final int by)
{
int bi = binindex(bx,by);
WeakReference<Bin> r = bins.get(bi);
if (r == null) return null;
Bin b = r.get();
if (b == null) return null;
return b.candidates.iterator();
}
public Iterator<Candidate> getBin(final int bx, final int by, final boolean cutLeft,
final boolean cutAbove)
{
final Iterator<Candidate> b = getBin(bx,by);
if (b == null) return null;
return new Iterator<Candidate>()
{
private Candidate c = findNext();
public boolean hasNext()
{
return c != null;
}
public Candidate next()
{
if (c == null) throw new NoSuchElementException();
Candidate r = c;
c = findNext();
return r;
}
private Candidate findNext()
{
while (true)
{
if (!b.hasNext()) return null;
Candidate bc = b.next();
if ((cutLeft && bc.bounds.x >> binShift != bx)
|| (cutAbove && bc.bounds.y >> binShift != by)) continue;
return bc;
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
public Iterator<Candidate> intersect(final int bx, final int by, final Rectangle r,
final boolean cut)
{
final Iterator<Candidate> b = getBin(bx,by,cut && r.x >> binShift != bx,cut
&& r.y >> binShift != by);
if (b == null) return null;
return new Iterator<Candidate>()
{
private Candidate c = findNext();
public boolean hasNext()
{
return c != null;
}
public Candidate next()
{
if (c == null) throw new NoSuchElementException();
Candidate r = c;
c = findNext();
return r;
}
private Candidate findNext()
{
while (true)
{
if (!b.hasNext()) return null;
Candidate bc = b.next();
if (bc.bounds.intersects(r)) return bc;
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
public Iterator<CandidateBin> intersect(final Rectangle r, final boolean cut)
{
return new Iterator<CandidateBin>()
{
private boolean inside;
private Iterator<Candidate> b;
private boolean end = false;
private int x0 = r.x >> binShift;
private int x1 = r.x + r.width - 1 >> binShift;
private int y0 = r.y >> binShift;
private int y1 = r.y + r.height - 1 >> binShift;
private int bx = x0 - 1;
private int by = y0;
private CandidateBin c = findNext();
public boolean hasNext()
{
return c != null;
}
public CandidateBin next()
{
if (c == null) throw new NoSuchElementException();
CandidateBin r = c;
c = findNext();
return r;
}
private void nextBin()
{
bx++;
if (bx > x1)
{
by++;
if (by > y1)
{
b = null;
end = true;
return;
}
bx = x0;
}
inside = (bx << binShift >= r.x && by << binShift >= r.y && bx < x1 - 1 && by < y1 - 1);
b = inside ? (cut ? getBin(bx,by,true,true) : getBin(bx,by)) : intersect(bx,by,r,cut);
}
private CandidateBin findNext()
{
while (true)
{
nextBin();
if (b == null)
{
if (end) return null;
continue;
}
return new CandidateBin(bx << binShift,by << binShift,1 << binShift,1 << binShift,b);
}
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
public Iterator<CandidateBin> all(final boolean cut)
{
final Iterator<Entry<Integer,WeakReference<Bin>>> es = bins.entrySet().iterator();
return new Iterator<CandidateBin>()
{
private CandidateBin cb = findNext();
public boolean hasNext()
{
return cb != null;
}
public CandidateBin next()
{
if (cb == null) throw new NoSuchElementException();
CandidateBin r = cb;
cb = findNext();
return r;
}
private CandidateBin findNext()
{
if (!es.hasNext()) return null;
Entry<Integer,WeakReference<Bin>> e = es.next();
int i = e.getKey();
int bx = i << 16 >> 16;
int by = i >> 16;
Bin b = e.getValue().get();
if (b == null) return null;
return new CandidateBin(bx << binShift,by << binShift,1 << binShift,1 << binShift,
cut ? getBin(bx,by,true,true) : b.iterator());
}
public void remove()
{
throw new UnsupportedOperationException();
}
};
}
private static int binindex(int x, int y)
{
if (x << 16 >> 16 != x || y << 16 >> 16 != y) throw new IllegalArgumentException();
return x & -1 >>> 16 | y << 16;
}
public class Candidate implements Comparable<Candidate>
{
public Object data;
private int binx, biny, binw, binh;
private final Rectangle bounds = new Rectangle(-1,-1);
private int depth;
// If the candidate is selected, it should be always visible
private boolean isSelected = false;
private Bin[] cBins;
public void setDepth(int d)
{
setDepth(d,false);
}
public void setDepth(int d, boolean selected)
{
//if (depth == d) return;
if (cBins == null)
{
depth = d;
isSelected = selected;
return;
}
for (Bin b : cBins)
b.candidates.remove(this);
depth = d;
isSelected = selected;
for (Bin b : cBins)
b.candidates.add(this);
}
public void setBounds(Rectangle b)
{
bounds.setBounds(b);
int obx = binx;
int oby = biny;
int obw = binw;
int obh = binh;
binx = b.x >> binShift;
biny = b.y >> binShift;
binw = 1 + (b.x + b.width - 1 >> binShift) - binx;
binh = 1 + (b.y + b.height - 1 >> binShift) - biny;
if (binx == obx && biny == oby && binw == obw && binh == obh) return;
Bin[] ob = cBins;
cBins = new Bin[binw * binh];
int i = 0;
for (int y = 0; y < obh; y++)
for (int x = 0; x < obw; x++)
{
int xo = obx + x - binx;
if (xo < 0 || xo >= binw)
{
ob[i++].candidates.remove(this);
continue;
}
int yo = oby + y - biny;
if (yo < 0 || yo >= binh)
{
ob[i++].candidates.remove(this);
continue;
}
cBins[xo + binw * yo] = ob[i++];
}
i = 0;
for (int y = 0; y < binh; y++)
for (int x = 0; x < binw; x++)
{
if (cBins[i] == null)
{
int idx = binindex(binx + x,biny + y);
WeakReference<Bin> r = bins.get(idx);
Bin bin = r == null ? null : r.get();
if (bin == null)
{
bin = new Bin(idx);
}
cBins[i] = bin;
bin.candidates.add(this);
}
i++;
}
}
public Rectangle getBounds(Rectangle b)
{
if (b == null) return bounds.getBounds();
b.setBounds(bounds);
return b;
}
public void remove()
{
for (Bin b : cBins)
b.candidates.remove(this);
cBins = null;
bounds.setSize(-1,-1);
binw = 0;
binh = 0;
}
public int compareTo(Candidate c)
{
if (this == c) return 0;
// If the candidate is selected, it should be always visible
if (isSelected) return 1;
return c.depth > depth ? 1 : c.depth < depth ? -1
: new Integer(c.hashCode()).compareTo(hashCode());
}
}
public static abstract class LateralIterator<T> implements Iterator<T>
{
protected Iterator<T> iter;
public boolean hasNext()
{
if (iter == null || !iter.hasNext())
{
iter = getNextIterator();
if (iter == null) return false;
}
return true;
}
public T next()
{
return iter.next();
}
public void remove()
{
iter.remove();
}
protected abstract Iterator<T> getNextIterator();
}
public static final class CandidateIterator extends LateralIterator<Candidate>
{
final Iterator<CandidateBin> cbi;
public CandidateIterator(Iterator<CandidateBin> i)
{
cbi = i;
}
@Override
protected Iterator<Candidate> getNextIterator()
{
while (cbi.hasNext())
{
Iterator<Candidate> r = cbi.next().iterator;
if (r != null && r.hasNext()) return r;
}
return null;
}
}
/**
* Abstract Iterator wrapper/implementation which allows the individual elements
* to be converted or even bypassed as needed (by having convert() return null).
* This implementation will not return null elements.
* <p>
* Note that due to the way this iterator is implemented,
* dynamic removal of elements is not possible.
* @param <T1> The type of the wrapped iterator.
* @param <T2> The output (converted) type of the implementation iterator.
*/
public static abstract class ConversionIterator<T1, T2> implements Iterator<T2>
{
/** The wrapped iterator */
protected final Iterator<T1> iter;
/**
* Temporarily stores the next element that was able
* to convert, between calls to hasNext() and next()
*/
private T2 next;
/**
* Wraps the given iterator.
* @param t1 The iterator to wrap
*/
public ConversionIterator(Iterator<T1> t1)
{
iter = t1;
}
public boolean hasNext()
{
if (next == null) next = findNext();
return next != null;
}
public T2 next()
{
T2 n = next == null ? findNext() : next;
next = null;
return n;
}
@Deprecated
public void remove()
{
// Simply doing ci.remove() here wouldn't work if hasNext has been called.
throw new UnsupportedOperationException();
}
/**
* Prepares the next available converted element for iteration.
* @return The converted element.
*/
private T2 findNext()
{
while (iter.hasNext())
{
T1 c = iter.next();
T2 r = convert(c);
if (r != null) return r;
}
return null;
}
/**
* Converts a given element into the implementation type.
* Implementations may return null to entirely skip the element.
* The implementation iterator will skip to the next non-null conversion.
* @param c An element from the wrapped iterator.
* @return A converted element for the implementation iterator, or null.
*/
protected abstract T2 convert(T1 c);
}
public static final class CandidateDataIterator<T> extends ConversionIterator<Candidate,T>
{
private final Class<T> ct;
public CandidateDataIterator(Iterator<CandidateBin> i, Class<T> t)
{
super(new CandidateIterator(i));
ct = t;
}
@Override
protected T convert(Candidate c)
{
return ct.isInstance(c.data) ? ct.cast(c.data) : null;
}
}
public static final class CandidateDepthDataIterator<T> extends ConversionIterator<Candidate,T>
{
private final Class<T> ct;
private final int depth;
public CandidateDepthDataIterator(Iterator<CandidateBin> i, Class<T> t, int depth)
{
super(new CandidateIterator(i));
ct = t;
this.depth = depth;
}
@Override
protected T convert(Candidate c)
{
return ct.isInstance(c.data) && c.depth == depth ? ct.cast(c.data) : null;
}
}
public static final class CandidateBin
{
public final int x, y, w, h;
public final Iterator<Candidate> iterator;
public CandidateBin(int x, int y, int w, int h, Iterator<Candidate> i)
{
this.x = x;
this.y = y;
this.w = w;
this.h = h;
iterator = i;
}
}
public final class Bin
{
private TreeSet<Candidate> candidates = new TreeSet<Candidate>();
public final int index;
public final WeakReference<Bin> reference;
public Bin(int idx)
{
index = idx;
reference = new WeakReference<Bin>(this);
bins.put(idx,reference);
}
public Iterator<Candidate> iterator()
{
return candidates.iterator();
}
@Override
protected void finalize()
{
bins.remove(index,reference);
}
}
}