package com.revolsys.geometry.edgegraph; import com.revolsys.geometry.algorithm.CGAlgorithms; import com.revolsys.geometry.algorithm.CGAlgorithmsDD; import com.revolsys.geometry.geomgraph.Quadrant; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.util.Assert; /** * Represents a directed component of an edge in an {@link EdgeGraph}. * HalfEdges link vertices whose locations are defined by {@link Coordinates}s. * HalfEdges start at an <b>origin</b> vertex, * and terminate at a <b>destination</b> vertex. * HalfEdges always occur in symmetric pairs, with the {@link #sym()} method * giving access to the oppositely-oriented component. * HalfEdges and the methods on them form an edge algebra, * which can be used to traverse and query the topology * of the graph formed by the edges. * <p> * By design HalfEdges carry minimal information * about the actual usage of the graph they represent. * They can be subclassed to carry more information if required. * <p> * HalfEdges form a complete and consistent data structure by themselves, * but an {@link EdgeGraph} is useful to allow retrieving edges * by vertex and edge location, as well as ensuring * edges are created and linked appropriately. * * @author Martin Davis * */ public class HalfEdge { /** * Initialize a symmetric pair of halfedges. * Intended for use by {@link EdgeGraph} subclasses. * The edges are initialized to have each other * as the sym edge, and to have {@link next} pointers * which point to edge other. * This effectively creates a graph containing a single edge. * * @param e0 a halfedge * @param e1 a symmetric halfedge * @return the initialized edge e0 */ public static HalfEdge init(final HalfEdge e0, final HalfEdge e1) { // ensure only newly created edges can be initialized, to prevent // information loss if (e0.sym != null || e1.sym != null || e0.next != null || e1.next != null) { throw new IllegalStateException("Edges are already initialized"); } e0.init(e1); return e0; } /** * Creates a HalfEdge pair representing an edge * between two vertices located at coordinates p0 and p1. * * @param p0 a vertex coordinate * @param p1 a vertex coordinate * @return the HalfEdge with origin at p0 */ public static HalfEdge newHalfEdge(final Point p0, final Point p1) { final HalfEdge e0 = new HalfEdge(p0); final HalfEdge e1 = new HalfEdge(p1); e0.init(e1); return e0; } private HalfEdge next; private final Point orig; private HalfEdge sym; /** * Creates an edge originating from a given coordinate. * * @param orig the origin coordinate */ public HalfEdge(final Point orig) { this.orig = orig; } /** * Implements the total order relation: * <p> * The angle of edge a is greater than the angle of edge b, * where the angle of an edge is the angle made by * the first segment of the edge with the positive x-axis * <p> * When applied to a list of edges originating at the same point, * this produces a CCW ordering of the edges around the point. * <p> * Using the obvious algorithm of computing the angle is not robust, * since the angle calculation is susceptible to roundoff error. * A robust algorithm is: * <ul> * <li>First, compare the quadrants the edge vectors lie in. * If the quadrants are different, * it is trivial to determine which edge has a greater angle. * * <li>if the vectors lie in the same quadrant, the * {@link CGAlgorithms#computeOrientation(Point, Point, Point)} function * can be used to determine the relative orientation of the vectors. * </ul> */ public int compareAngularDirection(final HalfEdge e) { final double dx = deltaX(); final double dy = deltaY(); final double dx2 = e.deltaX(); final double dy2 = e.deltaY(); // same vector if (dx == dx2 && dy == dy2) { return 0; } final double quadrant = Quadrant.quadrant(dx, dy); final double quadrant2 = Quadrant.quadrant(dx2, dy2); // if the vectors are in different quadrants, determining the ordering is // trivial if (quadrant > quadrant2) { return 1; } if (quadrant < quadrant2) { return -1; } // vectors are in the same quadrant // Check relative orientation of direction vectors // this is > e if it is CCW of e /** * MD - 9 Aug 2010 It seems that the basic algorithm is slightly orientation * dependent, when computing the orientation of a point very close to a * line. This is possibly due to the arithmetic in the translation to the * origin. * * For instance, the following situation produces identical results in spite * of the inverse orientation of the line segment: * * Point p0 = new PointDouble((double)219.3649559090992, 140.84159161824724); * Point p1 = new PointDouble((double)168.9018919682399, -5.713787599646864); * * Point p = new PointDouble((double)186.80814046338352, 46.28973405831556); int * orient = orientationIndex(p0, p1, p); int orientInv = * orientationIndex(p1, p0, p); * * A way to force consistent results is to normalize the orientation of the * vector using the following code. However, this may make the results of * orientationIndex inconsistent through the triangle of points, so it's not * clear this is an appropriate patch. * */ return CGAlgorithmsDD.orientationIndex(e.orig, e.dest(), dest()); // testing only // return ShewchuksDeterminant.orientationIndex(p1, p2, q); // previous implementation - not quite fully robust // return RobustDeterminant.orientationIndex(p1, p2, q); } /** * Compares edges which originate at the same vertex * based on the angle they make at their origin vertex with the positive X-axis. * This allows sorting edges around their origin vertex in CCW order. */ public int compareTo(final Object obj) { final HalfEdge e = (HalfEdge)obj; final int comp = compareAngularDirection(e); return comp; } /** * Computes the degree of the origin vertex. * The degree is the number of edges * originating from the vertex. * * @return the degree of the origin vertex */ public int degree() { int degree = 0; HalfEdge e = this; do { degree++; e = e.oNext(); } while (e != this); return degree; } /** * The X component of the distance between the orig and dest vertices. * * @return the X component of the edge length */ public double deltaX() { return this.sym.orig.getX() - this.orig.getX(); } /** * The Y component of the distance between the orig and dest vertices. * * @return the Y component of the edge length */ public double deltaY() { return this.sym.orig.getY() - this.orig.getY(); } /** * Gets the destination coordinate of this edge. * * @return the destination coordinate */ public Point dest() { return this.sym.orig; } /** * Tests whether this edge has the given orig and dest vertices. * * @param p0 the origin vertex to test * @param p1 the destination vertex to test * @return true if the vertices are equal to the ones of this edge */ public boolean equals(final Point p0, final Point p1) { return this.orig.equals(2, p0) && this.sym.orig.equals(p1); } /** * Finds the edge starting at the origin of this edge * with the given dest vertex, * if any. * * @param dest the dest vertex to search for * @return the edge with the required dest vertex, if it exists, * or null */ public HalfEdge find(final Point dest) { HalfEdge oNext = this; do { if (oNext == null) { return null; } if (oNext.dest().equals(2, dest)) { return oNext; } oNext = oNext.oNext(); } while (oNext != this); return null; } protected void init(final HalfEdge e) { setSym(e); e.setSym(this); // set next ptrs for a single segment setNext(e); e.setNext(this); } /** * Inserts an edge * into the ring of edges around the origin vertex of this edge. * The inserted edge must have the same origin as this edge. * * @param e the edge to insert */ public void insert(final HalfEdge e) { // if no other edge around origin if (oNext() == this) { // set linkage so ring is correct insertAfter(e); return; } // otherwise, find edge to insert after final int ecmp = compareTo(e); HalfEdge ePrev = this; do { final HalfEdge oNext = ePrev.oNext(); final int cmp = oNext.compareTo(e); if (cmp != ecmp || oNext == this) { ePrev.insertAfter(e); return; } ePrev = oNext; } while (ePrev != this); Assert.shouldNeverReachHere(); } /** * Insert an edge with the same origin after this one. * Assumes that the inserted edge is in the correct * position around the ring. * * @param e the edge to insert (with same origin) */ private void insertAfter(final HalfEdge e) { Assert.equals(this.orig, e.orig()); final HalfEdge save = oNext(); this.sym.setNext(e); e.sym().setNext(save); } /** * Gets the next edge CCW around the * destination vertex of this edge. * If the vertex has degree 1 then this is the <b>sym</b> edge. * * @return the next edge */ public HalfEdge next() { return this.next; } public HalfEdge oNext() { return this.sym.next; } /** * Gets the origin coordinate of this edge. * * @return the origin coordinate */ public Point orig() { return this.orig; } /** * Returns the edge previous to this one * (with dest being the same as this orig). * * @return the previous edge to this one */ public HalfEdge prev() { return this.sym.next().sym; } /** * Finds the first node previous to this edge, if any. * If no such node exists (i.e the edge is part of a ring) * then null is returned. * * @return an edge originating at the node prior to this edge, if any, * or null if no node exists */ public HalfEdge prevNode() { HalfEdge e = this; while (e.degree() == 2) { e = e.prev(); if (e == this) { return null; } } return e; } public void setNext(final HalfEdge e) { this.next = e; } /** * Sets the sym edge. * * @param e the sym edge to set */ private void setSym(final HalfEdge e) { this.sym = e; } /** * Gets the symmetric pair edge of this edge. * * @return the symmetric pair edge */ public HalfEdge sym() { return this.sym; } /** * Computes a string representation of a HalfEdge. * * @return a string representation */ @Override public String toString() { return "HE(" + this.orig.getX() + " " + this.orig.getY() + ", " + this.sym.orig.getX() + " " + this.sym.orig.getY() + ")"; } }