package PaulChew;
/*
* Copyright (c) 2005, 2007 by L. Paul Chew.
*
* Permission is hereby granted, without written agreement and without
* license or royalty fees, to use, copy, modify, and distribute this
* software and its documentation for any purpose, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
/**
* A 2D Delaunay Triangulation (DT) with incremental site insertion.
*
* This is not the fastest way to build a DT, but it's a reasonable way to build
* a DT incrementally and it makes a nice interactive display. There are several
* O(n log n) methods, but they require that the sites are all known initially.
*
* A Triangulation is a Set of Triangles. A Triangulation is unmodifiable as a
* Set; the only way to change it is to add sites (via delaunayPlace).
*
* @author Paul Chew
*
* Created July 2005. Derived from an earlier, messier version.
*
* Modified November 2007. Rewrote to use AbstractSet as parent class and to use
* the Graph class internally. Tried to make the DT algorithm clearer by
* explicitly creating a cavity. Added code needed to find a Voronoi cell.
*
*/
public class Triangulation extends AbstractSet<Triangle> {
private Triangle mostRecent = null; // Most recently "active" triangle
private Graph<Triangle> triGraph; // Holds triangles for navigation
/**
* All sites must fall within the initial triangle.
* @param triangle the initial triangle
*/
public Triangulation (Triangle triangle) {
triGraph = new Graph<Triangle>();
triGraph.add(triangle);
mostRecent = triangle;
}
/* The following two methods are required by AbstractSet */
@Override
public Iterator<Triangle> iterator () {
return triGraph.nodeSet().iterator();
}
@Override
public int size () {
return triGraph.nodeSet().size();
}
@Override
public String toString () {
return "Triangulation with " + size() + " triangles";
}
/**
* True iff triangle is a member of this triangulation.
* This method isn't required by AbstractSet, but it improves efficiency.
* @param triangle the object to check for membership
*/
public boolean contains (Object triangle) {
return triGraph.nodeSet().contains(triangle);
}
/**
* Report neighbor opposite the given vertex of triangle.
* @param site a vertex of triangle
* @param triangle we want the neighbor of this triangle
* @return the neighbor opposite site in triangle; null if none
* @throws IllegalArgumentException if site is not in this triangle
*/
public Triangle neighborOpposite (Pnt site, Triangle triangle) {
if (!triangle.contains(site))
throw new IllegalArgumentException("Bad vertex; not in triangle");
for (Triangle neighbor: triGraph.neighbors(triangle)) {
if (!neighbor.contains(site)) return neighbor;
}
return null;
}
/**
* Return the set of triangles adjacent to triangle.
* @param triangle the triangle to check
* @return the neighbors of triangle
*/
public Set<Triangle> neighbors(Triangle triangle) {
return triGraph.neighbors(triangle);
}
/**
* Report triangles surrounding site in order (cw or ccw).
* @param site we want the surrounding triangles for this site
* @param triangle a "starting" triangle that has site as a vertex
* @return all triangles surrounding site in order (cw or ccw)
* @throws IllegalArgumentException if site is not in triangle
*/
public List<Triangle> surroundingTriangles (Pnt site, Triangle triangle) {
if (!triangle.contains(site))
throw new IllegalArgumentException("Site not in triangle");
List<Triangle> list = new ArrayList<Triangle>();
Triangle start = triangle;
Pnt guide = triangle.getVertexButNot(site); // Affects cw or ccw
while (true) {
list.add(triangle);
Triangle previous = triangle;
triangle = this.neighborOpposite(guide, triangle); // Next triangle
guide = previous.getVertexButNot(site, guide); // Update guide
if (triangle == start) break;
}
return list;
}
/**
* Locate the triangle with point inside it or on its boundary.
* @param point the point to locate
* @return the triangle that holds point; null if no such triangle
*/
public Triangle locate (Pnt point) {
Triangle triangle = mostRecent;
if (!this.contains(triangle)) triangle = null;
// Try a directed walk (this works fine in 2D, but can fail in 3D)
Set<Triangle> visited = new HashSet<Triangle>();
while (triangle != null) {
if (visited.contains(triangle)) { // This should never happen
System.out.println("Warning: Caught in a locate loop");
break;
}
visited.add(triangle);
// Corner opposite point
Pnt corner = point.isOutside(triangle.toArray(new Pnt[0]));
if (corner == null) return triangle;
triangle = this.neighborOpposite(corner, triangle);
}
// No luck; try brute force
System.out.println("Warning: Checking all triangles for " + point);
for (Triangle tri: this) {
if (point.isOutside(tri.toArray(new Pnt[0])) == null) return tri;
}
// No such triangle
System.out.println("Warning: No triangle holds " + point);
return null;
}
/**
* Place a new site into the DT.
* Nothing happens if the site matches an existing DT vertex.
* @param site the new Pnt
* @throws IllegalArgumentException if site does not lie in any triangle
*/
public void delaunayPlace (Pnt site) {
// Uses straightforward scheme rather than best asymptotic time
// Locate containing triangle
Triangle triangle = locate(site);
// Give up if no containing triangle or if site is already in DT
if (triangle == null)
throw new IllegalArgumentException("No containing triangle");
if (triangle.contains(site)) return;
// Determine the cavity and update the triangulation
Set<Triangle> cavity = getCavity(site, triangle);
mostRecent = update(site, cavity);
}
/**
* Determine the cavity caused by site.
* @param site the site causing the cavity
* @param triangle the triangle containing site
* @return set of all triangles that have site in their circumcircle
*/
private Set<Triangle> getCavity (Pnt site, Triangle triangle) {
Set<Triangle> encroached = new HashSet<Triangle>();
Queue<Triangle> toBeChecked = new LinkedList<Triangle>();
Set<Triangle> marked = new HashSet<Triangle>();
toBeChecked.add(triangle);
marked.add(triangle);
while (!toBeChecked.isEmpty()) {
triangle = toBeChecked.remove();
if (site.vsCircumcircle(triangle.toArray(new Pnt[0])) == 1)
continue; // Site outside triangle => triangle not in cavity
encroached.add(triangle);
// Check the neighbors
for (Triangle neighbor: triGraph.neighbors(triangle)){
if (marked.contains(neighbor)) continue;
marked.add(neighbor);
toBeChecked.add(neighbor);
}
}
return encroached;
}
/**
* Update the triangulation by removing the cavity triangles and then
* filling the cavity with new triangles.
* @param site the site that created the cavity
* @param cavity the triangles with site in their circumcircle
* @return one of the new triangles
*/
private Triangle update (Pnt site, Set<Triangle> cavity) {
Set<Set<Pnt>> boundary = new HashSet<Set<Pnt>>();
Set<Triangle> theTriangles = new HashSet<Triangle>();
// Find boundary facets and adjacent triangles
for (Triangle triangle: cavity) {
theTriangles.addAll(neighbors(triangle));
for (Pnt vertex: triangle) {
Set<Pnt> facet = triangle.facetOpposite(vertex);
if (boundary.contains(facet)) boundary.remove(facet);
else boundary.add(facet);
}
}
theTriangles.removeAll(cavity); // Adj triangles only
// Remove the cavity triangles from the triangulation
for (Triangle triangle: cavity) triGraph.remove(triangle);
// Build each new triangle and add it to the triangulation
Set<Triangle> newTriangles = new HashSet<Triangle>();
for (Set<Pnt> vertices: boundary) {
vertices.add(site);
Triangle tri = new Triangle(vertices);
triGraph.add(tri);
newTriangles.add(tri);
}
// Update the graph links for each new triangle
theTriangles.addAll(newTriangles); // Adj triangle + new triangles
for (Triangle triangle: newTriangles)
for (Triangle other: theTriangles)
if (triangle.isNeighbor(other))
triGraph.add(triangle, other);
// Return one of the new triangles
return newTriangles.iterator().next();
}
/**
* Main program; used for testing.
*/
public static void main (String[] args) {
Triangle tri =
new Triangle(new Pnt(-10,10), new Pnt(10,10), new Pnt(0,-10));
System.out.println("Triangle created: " + tri);
Triangulation dt = new Triangulation(tri);
System.out.println("DelaunayTriangulation created: " + dt);
dt.delaunayPlace(new Pnt(0,0));
dt.delaunayPlace(new Pnt(1,0));
dt.delaunayPlace(new Pnt(0,1));
System.out.println("After adding 3 points, we have a " + dt);
Triangle.moreInfo = true;
System.out.println("Triangles: " + dt.triGraph.nodeSet());
}
}