/*
* Copyright 2014 MovingBlocks
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.math.delaunay;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.terasology.math.geom.Polygon;
import org.terasology.math.geom.Rect2f;
import org.terasology.math.geom.Vector2f;
import org.terasology.math.geom.Winding;
public final class Site implements ICoord {
private static final float EPSILON = .005f;
private Vector2f coord;
private int siteIndex;
/**
* the edges that define this Site's Voronoi region:
*/
private List<Edge> edges = new ArrayList<Edge>();
/**
* which end of each edge hooks up with the previous edge in _edges:
*/
private List<LR> edgeOrientations;
/**
* ordered list of points that define the region clipped to bounds:
*/
private List<Vector2f> region;
public Site(Vector2f p, int index) {
coord = p;
siteIndex = index;
}
public static void sortSites(List<Site> sites) {
//sites.sort(Site.compare);
Collections.sort(sites, new Comparator<Site>() {
@Override
public int compare(Site o1, Site o2) {
return (int) Site.compare(o1, o2);
}
});
}
/**
* 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...
*
*/
private static float 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 boolean closeEnough(Vector2f p0, Vector2f p1) {
return p0.distanceSquared(p1) < EPSILON * EPSILON;
}
@Override
public Vector2f getCoord() {
return coord;
}
@Override
public String toString() {
return "Site " + siteIndex + ": " + getCoord();
}
void addEdge(Edge edge) {
getEdges().add(edge);
}
public Edge nearestEdge() {
// _edges.sort(Edge.compareSitesDistances);
Collections.sort(getEdges(), new Comparator<Edge>() {
@Override
public int compare(Edge o1, Edge o2) {
return (int) Edge.compareSitesDistances(o1, o2);
}
});
return getEdges().get(0);
}
List<Site> neighborSites() {
if (getEdges() == null || getEdges().isEmpty()) {
return Collections.emptyList();
}
if (edgeOrientations == null) {
reorderEdges();
}
List<Site> list = new ArrayList<Site>();
for (Edge edge : getEdges()) {
list.add(neighborSite(edge));
}
return list;
}
private Site neighborSite(Edge edge) {
if (this == edge.getLeftSite()) {
return edge.getRightSite();
}
if (this == edge.getRightSite()) {
return edge.getLeftSite();
}
return null;
}
List<Vector2f> region(Rect2f clippingBounds) {
if (getEdges() == null || getEdges().isEmpty()) {
return Collections.emptyList();
}
if (edgeOrientations == null) {
reorderEdges();
region = clipToBounds(clippingBounds);
if ((Polygon.createCopy(region)).winding() == Winding.CLOCKWISE) {
Collections.reverse(region);
}
}
return region;
}
private void reorderEdges() {
//trace("_edges:", _edges);
EdgeReorderer reorderer = new EdgeReorderer(edges, Vertex.class);
edges = reorderer.getEdges();
//trace("reordered:", _edges);
edgeOrientations = reorderer.getEdgeOrientations();
reorderer.dispose();
}
private List<Vector2f> clipToBounds(Rect2f bounds) {
List<Vector2f> points = new ArrayList<Vector2f>();
int n = getEdges().size();
int i = 0;
Edge edge;
while (i < n && (!getEdges().get(i).isVisible())) {
++i;
}
if (i == n) {
// no edges visible
return Collections.emptyList();
}
edge = getEdges().get(i);
LR orientation = edgeOrientations.get(i);
points.add(edge.getClippedEnds().get(orientation));
points.add(edge.getClippedEnds().get((orientation.other())));
for (int j = i + 1; j < n; ++j) {
edge = getEdges().get(j);
if (!edge.isVisible()) {
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(List<Vector2f> points, int j, Rect2f bounds, boolean closingUp) {
Vector2f rightPoint = points.get(points.size() - 1);
Edge newEdge = getEdges().get(j);
LR newOrientation = edgeOrientations.get(j);
// the point that must be connected to rightPoint:
Vector2f newPoint = newEdge.getClippedEnds().get(newOrientation);
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.getX() != newPoint.getX()
&& rightPoint.getY() != newPoint.getY()) {
// 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);
float px;
float py;
if ((rightCheck & BoundsCheck.RIGHT) != 0) {
px = bounds.maxX();
if ((newCheck & BoundsCheck.BOTTOM) != 0) {
py = bounds.maxY();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.TOP) != 0) {
py = bounds.minY();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.LEFT) != 0) {
if (rightPoint.getY() - bounds.minY() + newPoint.getY() - bounds.minY() < bounds.height()) {
py = bounds.minY();
} else {
py = bounds.maxY();
}
points.add(new Vector2f(px, py));
points.add(new Vector2f(bounds.minX(), py));
}
} else if ((rightCheck & BoundsCheck.LEFT) != 0) {
px = bounds.minX();
if ((newCheck & BoundsCheck.BOTTOM) != 0) {
py = bounds.maxY();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.TOP) != 0) {
py = bounds.minY();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.RIGHT) != 0) {
if (rightPoint.getY() - bounds.minY() + newPoint.getY() - bounds.minY() < bounds.height()) {
py = bounds.minY();
} else {
py = bounds.maxY();
}
points.add(new Vector2f(px, py));
points.add(new Vector2f(bounds.maxX(), py));
}
} else if ((rightCheck & BoundsCheck.TOP) != 0) {
py = bounds.minY();
if ((newCheck & BoundsCheck.RIGHT) != 0) {
px = bounds.maxX();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.LEFT) != 0) {
px = bounds.minX();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.BOTTOM) != 0) {
if (rightPoint.getX() - bounds.minX() + newPoint.getX() - bounds.minX() < bounds.width()) {
px = bounds.minX();
} else {
px = bounds.maxX();
}
points.add(new Vector2f(px, py));
points.add(new Vector2f(px, bounds.maxY()));
}
} else if ((rightCheck & BoundsCheck.BOTTOM) != 0) {
py = bounds.maxY();
if ((newCheck & BoundsCheck.RIGHT) != 0) {
px = bounds.maxX();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.LEFT) != 0) {
px = bounds.minX();
points.add(new Vector2f(px, py));
} else if ((newCheck & BoundsCheck.TOP) != 0) {
if (rightPoint.getX() - bounds.minX() + newPoint.getX() - bounds.minX() < bounds.width()) {
px = bounds.minX();
} else {
px = bounds.maxX();
}
points.add(new Vector2f(px, py));
points.add(new Vector2f(px, bounds.minY()));
}
}
}
if (closingUp) {
// newEdge's ends have already been added
return;
}
points.add(newPoint);
}
Vector2f newRightPoint = newEdge.getClippedEnds().get(newOrientation.other());
if (!closeEnough(points.get(0), newRightPoint)) {
points.add(newRightPoint);
}
}
public float getX() {
return coord.x();
}
public float getY() {
return coord.y();
}
/**
* @return the edges
*/
public List<Edge> getEdges() {
return edges;
}
private static final class BoundsCheck {
public static final int TOP = 1;
public static final int BOTTOM = 2;
public static final int LEFT = 4;
public static final int RIGHT = 8;
private BoundsCheck() {
}
/**
*
* @param point
* @param bounds
* @return an int with the appropriate bits set if the Point lies on the
* corresponding bounds lines
*
*/
static int check(Vector2f point, Rect2f bounds) {
int value = 0;
if (point.getX() == bounds.minX()) {
value |= LEFT;
}
if (point.getX() == bounds.maxX()) {
value |= RIGHT;
}
if (point.getY() == bounds.minY()) {
value |= TOP;
}
if (point.getY() == bounds.maxY()) {
value |= BOTTOM;
}
return value;
}
}
}