package com.bioxx.jmapgen.com.nodename.delaunay;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.Comparator;
import java.util.Vector;
import com.bioxx.jmapgen.Point;
import com.bioxx.jmapgen.com.nodename.geom.Polygon;
import com.bioxx.jmapgen.com.nodename.geom.Winding;
/**
* Sites are used by edges to define which points that the edge is bisecting.
* @author Bioxx
*
*/
public class Site implements ICoord, Comparator<Site>
{
private static Vector<Site> _pool = new Vector<Site>();
public Site()
{
}
public static Site create(Point p, int index)
{
if (_pool.size() > 0)
{
Site site = _pool.lastElement().init(p, index);
_pool.remove(_pool.size()-1);
return site;
}
else
{
return new Site(p, index);
}
}
/**
* sort sites on y, then x, coord
* also change each site's _siteIndex to match its new position in the list
* so the _siteIndex can be used to identify the site for nearest-neighbor queries
*
* haha "also" - means more than one responsibility...
*
*/
@Override
public int compare(Site s1, Site s2)
{
int returnValue = Voronoi.compareByYThenX(s1, s2);
// swap _siteIndex values if necessary to match new ordering:
int tempIndex;
if (returnValue == -1)
{
if (s1._siteIndex > s2._siteIndex)
{
tempIndex = s1._siteIndex;
s1._siteIndex = s2._siteIndex;
s2._siteIndex = tempIndex;
}
}
else if (returnValue == 1)
{
if (s2._siteIndex > s1._siteIndex)
{
tempIndex = s2._siteIndex;
s2._siteIndex = s1._siteIndex;
s1._siteIndex = tempIndex;
}
}
return returnValue;
}
private static final double EPSILON = 0.005;
private static boolean closeEnough(Point p0, Point p1)
{
return Point.distance(p0, p1) < EPSILON;
}
private Point _coord;
public Point getCoord()
{
return _coord;
}
private int _siteIndex;
// the edges that define this Site's Voronoi region:
private Vector<Edge> _edges;
Vector<Edge> getEdges()
{
return _edges;
}
// which end of each edge hooks up with the previous edge in _edges:
private Vector<LR> _edgeOrientations;
// ordered list of points that define the region clipped to bounds:
private Vector<Point> _region;
public Site(Point p, int index)
{
init(p, index);
}
private Site init(Point p, int index)
{
_coord = p;
_siteIndex = index;
_edges = new Vector<Edge>();
_region = null;
return this;
}
public String toString()
{
return "Site " + _siteIndex + ": " + _coord;
}
public void addEdge(Edge edge)
{
_edges.add(edge);
}
public Edge nearestEdge()
{
Collections.sort(_edges, new Edge());
return _edges.firstElement();
}
public Vector<Site> neighborSites()
{
if (_edges == null || _edges.size() == 0)
{
return new Vector<Site>();
}
if (_edgeOrientations == null)
{
reorderEdges();
}
Vector<Site> list = new Vector<Site>();
for(int i = 0; i < _edges.size(); i++)
{
list.add(neighborSite(_edges.get(i)));
}
return list;
}
private Site neighborSite(Edge edge)
{
if (this == edge.getLeftSite())
{
return edge.getRightSite();
}
if (this == edge.getRightSite())
{
return edge.getLeftSite();
}
return null;
}
Vector<Point> region(Rectangle clippingBounds)
{
if (_edges == null || _edges.size() == 0)
{
return new Vector<Point>();
}
if (_edgeOrientations == null)
{
reorderEdges();
_region = clipToBounds(clippingBounds);
if ((new Polygon(_region)).winding() == Winding.CLOCKWISE)
{
_region = reverseVector(_region);
}
}
return _region;
}
Vector<Point> reverseVector(Vector<Point> v0)
{
Vector<Point> v1 = new Vector<Point>();
for(int iter = v0.size()-1; iter >= 0; iter--)
{
v1.add(v0.get(iter));
}
return v1;
}
private void reorderEdges()
{
try {
EdgeReorderer reorderer = new EdgeReorderer(_edges, Vertex.class);
_edges = reorderer.getEdges();
_edgeOrientations = reorderer.getEdgeOrientations();
} catch (Exception e) {
e.printStackTrace();
}
}
private Vector<Point> clipToBounds(Rectangle bounds)
{
Vector<Point> points = new Vector<Point>();
int n = _edges.size();
int i = 0;
Edge edge;
while (i < n && _edges.get(i).getVisible() == false)
{
++i;
}
if (i == n)
{
// no edges visible
return new Vector<Point>();
}
edge = _edges.get(i);
LR orientation = _edgeOrientations.get(i);
points.add(edge.getClippedEnds()[orientation.value]);
points.add(edge.getClippedEnds()[LR.other(orientation).value]);
for (int j = i + 1; j < n; ++j)
{
edge = _edges.get(j);
if (edge.getVisible() == false)
{
continue;
}
connect(points, j, bounds, false);
}
// close up the polygon by adding another corner point of the bounds if needed:
connect(points, i, bounds, true);
return points;
}
private void connect(Vector<Point> points, int j, Rectangle bounds, Boolean closingUp)
{
Point rightPoint = points.get(points.size() - 1);
Edge newEdge = _edges.get(j);
LR newOrientation = _edgeOrientations.get(j);
// the point that must be connected to rightPoint:
Point newPoint = newEdge.getClippedEnds()[newOrientation.value];
if (!closeEnough(rightPoint, newPoint))
{
// The points do not coincide, so they must have been clipped at the bounds;
// see if they are on the same border of the bounds:
if (rightPoint.x != newPoint.x
&& rightPoint.y != newPoint.y)
{
// They are on different borders of the bounds;
// insert one or two corners of bounds as needed to hook them up:
// (NOTE this will not be correct if the region should take up more than
// half of the bounds rect, for then we will have gone the wrong way
// around the bounds and included the smaller part rather than the larger)
int rightCheck = BoundsCheck.check(rightPoint, bounds);
int newCheck = BoundsCheck.check(newPoint, bounds);
double px, py;
if (rightCheck == BoundsCheck.RIGHT)
{
px = bounds.getMaxX();
if (newCheck == BoundsCheck.BOTTOM)
{
py = bounds.getMaxY();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.TOP)
{
py = bounds.getMinY();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.LEFT)
{
if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height)
{
py = bounds.getMinY();
}
else
{
py = bounds.getMaxY();
}
points.add(new Point(px, py));
points.add(new Point(bounds.getMinX(), py));
}
}
else if (rightCheck == BoundsCheck.LEFT)
{
px = bounds.getMinX();
if (newCheck == BoundsCheck.BOTTOM)
{
py = bounds.getMaxY();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.TOP)
{
py = bounds.getMinY();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.RIGHT)
{
if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height)
{
py = bounds.getMinY();
}
else
{
py = bounds.getMaxY();
}
points.add(new Point(px, py));
points.add(new Point(bounds.getMaxX(), py));
}
}
else if (rightCheck == BoundsCheck.TOP)
{
py = bounds.getMinY();
if (newCheck == BoundsCheck.RIGHT)
{
px = bounds.getMaxX();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.LEFT)
{
px = bounds.getMinX();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.BOTTOM)
{
if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width)
{
px = bounds.getMinX();
}
else
{
px = bounds.getMaxX();
}
points.add(new Point(px, py));
points.add(new Point(px, bounds.getMaxY()));
}
}
else if (rightCheck == BoundsCheck.BOTTOM)
{
py = bounds.getMaxY();
if (newCheck == BoundsCheck.RIGHT)
{
px = bounds.getMaxX();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.LEFT)
{
px = bounds.getMinX();
points.add(new Point(px, py));
}
else if (newCheck == BoundsCheck.TOP)
{
if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width)
{
px = bounds.getMinX();
}
else
{
px = bounds.getMaxX();
}
points.add(new Point(px, py));
points.add(new Point(px, bounds.getMinY()));
}
}
}
if (closingUp)
{
// newEdge's ends have already been added
return;
}
points.add(newPoint);
}
Point newRightPoint = newEdge.getClippedEnds()[LR.other(newOrientation).value];
if (!closeEnough(points.get(0), newRightPoint))
{
points.add(newRightPoint);
}
}
double getX()
{
return _coord.x;
}
double getY()
{
return _coord.y;
}
double dist(ICoord p)
{
return Point.distance(p.getCoord(), this._coord);
}
}