/*
* 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.math.geom.BaseVector2f;
import org.terasology.math.geom.Circle;
import org.terasology.math.geom.LineSegment;
import org.terasology.math.geom.Rect2f;
import org.terasology.math.geom.Vector2f;
public final class Voronoi {
private static final Logger logger = LoggerFactory.getLogger(Voronoi.class);
private SiteList sites;
private Map<Vector2f, Site> sitesIndexedByLocation;
private final List<Edge> edges = new ArrayList<Edge>();
// TODO generalize this so it doesn't have to be a rectangle;
// then we can make the fractal voronois-within-voronois
private Rect2f plotBounds;
public Voronoi(List<Vector2f> points, Rect2f plotBounds) {
init(points, plotBounds);
fortunesAlgorithm();
}
public Voronoi(List<Vector2f> points) {
float maxWidth = 0;
float maxHeight = 0;
for (Vector2f p : points) {
maxWidth = Math.max(maxWidth, p.getX());
maxHeight = Math.max(maxHeight, p.getY());
}
logger.debug(maxWidth + "," + maxHeight);
init(points, Rect2f.createFromMinAndSize(0, 0, maxWidth, maxHeight));
fortunesAlgorithm();
}
public Voronoi(int numSites, float maxWidth, float maxHeight, Random r) {
List<Vector2f> points = new ArrayList<Vector2f>();
for (int i = 0; i < numSites; i++) {
points.add(new Vector2f(r.nextFloat() * maxWidth, r.nextFloat() * maxHeight));
}
init(points, Rect2f.createFromMinAndSize(0, 0, maxWidth, maxHeight));
fortunesAlgorithm();
}
public Rect2f getPlotBounds() {
return plotBounds;
}
private void init(List<Vector2f> points, Rect2f bounds) {
sites = new SiteList();
sitesIndexedByLocation = new HashMap<Vector2f, Site>();
addSites(points);
this.plotBounds = bounds;
}
private void addSites(List<Vector2f> points) {
int length = points.size();
for (int i = 0; i < length; ++i) {
addSite(points.get(i), i);
}
}
private void addSite(Vector2f p, int index) {
Site site = new Site(p, index);
sites.push(site);
sitesIndexedByLocation.put(p, site);
}
public List<Edge> edges() {
return edges;
}
public List<Vector2f> region(Vector2f p) {
Site site = sitesIndexedByLocation.get(p);
if (site == null) {
return Collections.emptyList();
}
return site.region(plotBounds);
}
// TODO: bug: if you call this before you call region(), something goes wrong :(
public List<Vector2f> neighborSitesForSite(Vector2f coord) {
List<Vector2f> points = new ArrayList<Vector2f>();
Site site = sitesIndexedByLocation.get(coord);
if (site == null) {
return points;
}
List<Site> sts = site.neighborSites();
for (Site neighbor : sts) {
points.add(neighbor.getCoord());
}
return points;
}
public List<Circle> circles() {
return sites.circles();
}
private List<Edge> selectEdgesForSitePoint(Vector2f coord, List<Edge> edgesToTest) {
List<Edge> filtered = new ArrayList<Edge>();
for (Edge e : edgesToTest) {
if (((e.getLeftSite() != null && e.getLeftSite().getCoord() == coord)
|| (e.getRightSite() != null && e.getRightSite().getCoord() == coord))) {
filtered.add(e);
}
}
return filtered;
}
private List<LineSegment> visibleLineSegments(List<Edge> edgs) {
List<LineSegment> segments = new ArrayList<LineSegment>();
for (Edge edge : edgs) {
if (edge.isVisible()) {
Vector2f p1 = edge.getClippedEnds().get(LR.LEFT);
Vector2f p2 = edge.getClippedEnds().get(LR.RIGHT);
segments.add(new LineSegment(p1, p2));
}
}
return segments;
}
private List<LineSegment> delaunayLinesForEdges(List<Edge> edgs) {
List<LineSegment> segments = new ArrayList<LineSegment>();
for (Edge edge : edgs) {
segments.add(edge.delaunayLine());
}
return segments;
}
public List<LineSegment> voronoiBoundaryForSite(Vector2f coord) {
return visibleLineSegments(selectEdgesForSitePoint(coord, edges));
}
public List<LineSegment> delaunayLinesForSite(Vector2f coord) {
return delaunayLinesForEdges(selectEdgesForSitePoint(coord, edges));
}
public List<LineSegment> voronoiDiagram() {
return visibleLineSegments(edges);
}
public List<LineSegment> hull() {
return delaunayLinesForEdges(hullEdges());
}
private List<Edge> hullEdges() {
List<Edge> filtered = new ArrayList<Edge>();
for (Edge e : edges) {
if (e.isPartOfConvexHull()) {
filtered.add(e);
}
}
return filtered;
/*function myTest(edge:Edge, index:int, vector:Vector.<Edge>):Boolean
{
return (edge.isPartOfConvexHull());
}*/
}
public List<Vector2f> hullPointsInOrder() {
List<Edge> hullEdges = hullEdges();
List<Vector2f> points = new ArrayList<Vector2f>();
if (hullEdges.isEmpty()) {
return points;
}
EdgeReorderer reorderer = new EdgeReorderer(hullEdges, Site.class);
hullEdges = reorderer.getEdges();
List<LR> orientations = reorderer.getEdgeOrientations();
reorderer.dispose();
LR orientation;
int n = hullEdges.size();
for (int i = 0; i < n; ++i) {
Edge edge = hullEdges.get(i);
orientation = orientations.get(i);
points.add(edge.getSite(orientation).getCoord());
}
return points;
}
public List<List<Vector2f>> regions() {
return sites.regions(plotBounds);
}
public List<Vector2f> siteCoords() {
return sites.siteCoords();
}
private void fortunesAlgorithm() {
Site newSite;
Site bottomSite;
Site topSite;
Site tempSite;
Vertex v;
Vertex vertex;
Vector2f newintstar = null;
LR leftRight;
Halfedge lbnd;
Halfedge rbnd;
Halfedge llbnd;
Halfedge rrbnd;
Halfedge bisector;
Edge edge;
Rect2f dataBounds = sites.getSitesBounds();
int sqrtNumSites = (int) Math.sqrt(sites.getLength() + 4);
HalfedgePriorityQueue heap = new HalfedgePriorityQueue(dataBounds.minY(), dataBounds.height(), sqrtNumSites);
EdgeList edgeList = new EdgeList(dataBounds.minX(), dataBounds.width(), sqrtNumSites);
List<Halfedge> halfEdges = new ArrayList<Halfedge>();
List<Vertex> vertices = new ArrayList<Vertex>();
Site bottomMostSite = sites.next();
newSite = sites.next();
for (;;) {
if (!heap.empty()) {
newintstar = heap.min();
}
if (newSite != null
&& (heap.empty() || compareByYThenX(newSite, newintstar) < 0)) {
/* new site is smallest */
//trace("smallest: new site " + newSite);
// Step 8:
lbnd = edgeList.edgeListLeftNeighbor(newSite.getCoord()); // the Halfedge just to the left of newSite
//trace("lbnd: " + lbnd);
rbnd = lbnd.edgeListRightNeighbor; // the Halfedge just to the right
//trace("rbnd: " + rbnd);
bottomSite = rightRegion(lbnd, bottomMostSite); // this is the same as leftRegion(rbnd)
// this Site determines the region containing the new site
//trace("new Site is in region of existing site: " + bottomSite);
// Step 9:
edge = Edge.createBisectingEdge(bottomSite, newSite);
//trace("new edge: " + edge);
edges.add(edge);
bisector = Halfedge.create(edge, LR.LEFT);
halfEdges.add(bisector);
// inserting two Halfedges into edgeList constitutes Step 10:
// insert bisector to the right of lbnd:
edgeList.insert(lbnd, bisector);
// first half of Step 11:
vertex = Vertex.intersect(lbnd, bisector);
if (vertex != null) {
vertices.add(vertex);
heap.remove(lbnd);
lbnd.vertex = vertex;
lbnd.ystar = vertex.getY() + BaseVector2f.distance(newSite.getCoord(), vertex.getCoord());
heap.insert(lbnd);
}
lbnd = bisector;
bisector = Halfedge.create(edge, LR.RIGHT);
halfEdges.add(bisector);
// second Halfedge for Step 10:
// insert bisector to the right of lbnd:
edgeList.insert(lbnd, bisector);
// second half of Step 11:
vertex = Vertex.intersect(bisector, rbnd);
if (vertex != null) {
vertices.add(vertex);
bisector.vertex = vertex;
bisector.ystar = vertex.getY() + BaseVector2f.distance(newSite.getCoord(), vertex.getCoord());
heap.insert(bisector);
}
newSite = sites.next();
} else if (!heap.empty()) {
/* intersection is smallest */
lbnd = heap.extractMin();
llbnd = lbnd.edgeListLeftNeighbor;
rbnd = lbnd.edgeListRightNeighbor;
rrbnd = rbnd.edgeListRightNeighbor;
bottomSite = leftRegion(lbnd, bottomMostSite);
topSite = rightRegion(rbnd, bottomMostSite);
// these three sites define a Delaunay triangle
// (not actually using these for anything...)
//_triangles.push(new Triangle(bottomSite, topSite, rightRegion(lbnd)));
v = lbnd.vertex;
lbnd.edge.setVertex(lbnd.leftRight, v);
rbnd.edge.setVertex(rbnd.leftRight, v);
edgeList.remove(lbnd);
heap.remove(rbnd);
edgeList.remove(rbnd);
leftRight = LR.LEFT;
if (bottomSite.getY() > topSite.getY()) {
tempSite = bottomSite;
bottomSite = topSite;
topSite = tempSite;
leftRight = LR.RIGHT;
}
edge = Edge.createBisectingEdge(bottomSite, topSite);
edges.add(edge);
bisector = Halfedge.create(edge, leftRight);
halfEdges.add(bisector);
edgeList.insert(llbnd, bisector);
edge.setVertex(leftRight.other(), v);
vertex = Vertex.intersect(llbnd, bisector);
if (vertex != null) {
vertices.add(vertex);
heap.remove(llbnd);
llbnd.vertex = vertex;
llbnd.ystar = vertex.getY() + BaseVector2f.distance(bottomSite.getCoord(), vertex.getCoord());
heap.insert(llbnd);
}
vertex = Vertex.intersect(bisector, rrbnd);
if (vertex != null) {
vertices.add(vertex);
bisector.vertex = vertex;
bisector.ystar = vertex.getY() + BaseVector2f.distance(bottomSite.getCoord(), vertex.getCoord());
heap.insert(bisector);
}
} else {
break;
}
}
// heap should be empty now
heap.dispose();
for (Halfedge halfEdge : halfEdges) {
halfEdge.reallyDispose();
}
halfEdges.clear();
// we need the vertices to clip the edges
for (Edge e : edges) {
e.clipVertices(plotBounds);
}
// but we don't actually ever use them again!
vertices.clear();
}
Site leftRegion(Halfedge he, Site bottomMostSite) {
Edge edge = he.edge;
if (edge == null) {
return bottomMostSite;
}
return edge.getSite(he.leftRight);
}
Site rightRegion(Halfedge he, Site bottomMostSite) {
Edge edge = he.edge;
if (edge == null) {
return bottomMostSite;
}
return edge.getSite(he.leftRight.other());
}
public static int compareByYThenX(Site s1, Site s2) {
if (s1.getY() < s2.getY()) {
return -1;
}
if (s1.getY() > s2.getY()) {
return 1;
}
if (s1.getX() < s2.getX()) {
return -1;
}
if (s1.getX() > s2.getX()) {
return 1;
}
return 0;
}
public static int compareByYThenX(Site s1, Vector2f s2) {
if (s1.getY() < s2.getY()) {
return -1;
}
if (s1.getY() > s2.getY()) {
return 1;
}
if (s1.getX() < s2.getX()) {
return -1;
}
if (s1.getX() > s2.getX()) {
return 1;
}
return 0;
}
}