/* * 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.EnumMap; import java.util.Map; import org.terasology.math.geom.BaseVector2f; import org.terasology.math.geom.LineSegment; import org.terasology.math.geom.Rect2f; import org.terasology.math.geom.Vector2f; /** * The line segment connecting the two Sites is part of the Delaunay * triangulation; the line segment connecting the two Vertices is part of the * Voronoi diagram * * */ public final class Edge { public static final Edge DELETED = new Edge(); // the equation of the edge: ax + by = c private float a; private float b; private float c; // the two Voronoi vertices that the edge connects // (if one of them is null, the edge extends to infinity) private Vertex leftVertex; private Vertex rightVertex; /** * Once clipVertices() is called, this HashMap will hold two Points * representing the clipped coordinates of the left and right ends... */ private final Map<LR, Vector2f> clippedVertices = new EnumMap<LR, Vector2f>(LR.class); /** * The two input Sites for which this Edge is a bisector: */ private final Map<LR, Site> sites = new EnumMap<LR, Site>(LR.class); private Edge() { } /** * This is the only way to create a new Edge * * @param site0 * @param site1 * @return * */ public static Edge createBisectingEdge(Site site0, Site site1) { float a; float b; float c; float dx = site1.getX() - site0.getX(); float dy = site1.getY() - site0.getY(); float absdx = dx > 0 ? dx : -dx; float absdy = dy > 0 ? dy : -dy; c = site0.getX() * dx + site0.getY() * dy + (dx * dx + dy * dy) * 0.5f; if (absdx > absdy) { a = 1.0f; b = dy / dx; c /= dx; } else { b = 1.0f; a = dx / dy; c /= dy; } Edge edge = new Edge(); edge.setLeftSite(site0); edge.setRightSite(site1); site0.addEdge(edge); site1.addEdge(edge); edge.leftVertex = null; edge.rightVertex = null; edge.a = a; edge.b = b; edge.c = c; //trace("createBisectingEdge: a ", edge.a, "b", edge.b, "c", edge.c); return edge; } public LineSegment delaunayLine() { // draw a line connecting the input Sites for which the edge is a bisector: return new LineSegment(getLeftSite().getCoord(), getRightSite().getCoord()); } public LineSegment voronoiEdge() { if (!isVisible()) { return null; } return new LineSegment(clippedVertices.get(LR.LEFT), clippedVertices.get(LR.RIGHT)); } public Vertex getLeftVertex() { return leftVertex; } public Vertex getRightVertex() { return rightVertex; } public void setVertex(LR leftRight, Vertex v) { if (leftRight == LR.LEFT) { leftVertex = v; } else { rightVertex = v; } } public boolean isPartOfConvexHull() { return (leftVertex == null || rightVertex == null); } public float sitesDistance() { return BaseVector2f.distance(getLeftSite().getCoord(), getRightSite().getCoord()); } public static float compareSitesDistancesMax(Edge edge0, Edge edge1) { float length0 = edge0.sitesDistance(); float length1 = edge1.sitesDistance(); if (length0 < length1) { return 1; } if (length0 > length1) { return -1; } return 0; } public static float compareSitesDistances(Edge edge0, Edge edge1) { return -compareSitesDistancesMax(edge0, edge1); } public Map<LR, Vector2f> getClippedEnds() { return clippedVertices; } /** * @return true unless the entire Edge is outside the bounds. */ public boolean isVisible() { return !clippedVertices.isEmpty(); } public void setLeftSite(Site s) { sites.put(LR.LEFT, s); } public Site getLeftSite() { return sites.get(LR.LEFT); } public void setRightSite(Site s) { sites.put(LR.RIGHT, s); } public Site getRightSite() { return sites.get(LR.RIGHT); } public Site getSite(LR leftRight) { return sites.get(leftRight); } @Override public String toString() { return "Edge [sites " + sites.get(LR.LEFT) + ", " + sites.get(LR.RIGHT) + "; endVertices " + leftVertex + ", " + rightVertex + "]"; } /** * Set _clippedVertices to contain the two ends of the portion of the * Voronoi edge that is visible within the bounds. If no part of the Edge * falls within the bounds, leave _clippedVertices null. * * @param bounds * */ public void clipVertices(Rect2f bounds) { float xmin = bounds.minX(); float ymin = bounds.minY(); float xmax = bounds.maxX(); float ymax = bounds.maxY(); Vertex vertex0; Vertex vertex1; float x0; float x1; float y0; float y1; if (getA() == 1.0 && getB() >= 0.0) { vertex0 = rightVertex; vertex1 = leftVertex; } else { vertex0 = leftVertex; vertex1 = rightVertex; } if (getA() == 1.0) { y0 = ymin; if (vertex0 != null && vertex0.getY() > ymin) { y0 = vertex0.getY(); } if (y0 > ymax) { return; } x0 = getC() - getB() * y0; y1 = ymax; if (vertex1 != null && vertex1.getY() < ymax) { y1 = vertex1.getY(); } if (y1 < ymin) { return; } x1 = getC() - getB() * y1; if ((x0 > xmax && x1 > xmax) || (x0 < xmin && x1 < xmin)) { return; } if (x0 > xmax) { x0 = xmax; y0 = (getC() - x0) / getB(); } else if (x0 < xmin) { x0 = xmin; y0 = (getC() - x0) / getB(); } if (x1 > xmax) { x1 = xmax; y1 = (getC() - x1) / getB(); } else if (x1 < xmin) { x1 = xmin; y1 = (getC() - x1) / getB(); } } else { x0 = xmin; if (vertex0 != null && vertex0.getX() > xmin) { x0 = vertex0.getX(); } if (x0 > xmax) { return; } y0 = getC() - getA() * x0; x1 = xmax; if (vertex1 != null && vertex1.getX() < xmax) { x1 = vertex1.getX(); } if (x1 < xmin) { return; } y1 = getC() - getA() * x1; if ((y0 > ymax && y1 > ymax) || (y0 < ymin && y1 < ymin)) { return; } if (y0 > ymax) { y0 = ymax; x0 = (getC() - y0) / getA(); } else if (y0 < ymin) { y0 = ymin; x0 = (getC() - y0) / getA(); } if (y1 > ymax) { y1 = ymax; x1 = (getC() - y1) / getA(); } else if (y1 < ymin) { y1 = ymin; x1 = (getC() - y1) / getA(); } } clippedVertices.clear(); if (vertex0 == leftVertex) { clippedVertices.put(LR.LEFT, new Vector2f(x0, y0)); clippedVertices.put(LR.RIGHT, new Vector2f(x1, y1)); } else { clippedVertices.put(LR.RIGHT, new Vector2f(x0, y0)); clippedVertices.put(LR.LEFT, new Vector2f(x1, y1)); } // overwrite previously computed coordinates with exact vertex locations // where possible. This avoids rounding errors and ensures that equals() works properly. // TODO: check before computing the clipped vertices if (leftVertex != null && bounds.contains(leftVertex.getX(), leftVertex.getY())) { clippedVertices.put(LR.LEFT, leftVertex.getCoord()); } if (rightVertex != null && bounds.contains(rightVertex.getX(), rightVertex.getY())) { clippedVertices.put(LR.RIGHT, rightVertex.getCoord()); } } /** * @return the a */ public float getA() { return a; } /** * @return the b */ public float getB() { return b; } /** * @return the c */ public float getC() { return c; } }