/* * This file is part of the Trickl Open Source Libraries. * * Trickl Open Source Libraries - http://open.trickl.com/ * * Copyright (C) 2011 Tim Gee. * * Trickl Open Source Libraries are free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Trickl Open Source Libraries are distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this project. If not, see <http://www.gnu.org/licenses/>. */ package com.trickl.graph.planar; import java.io.Serializable; import java.util.Iterator; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlID; import javax.xml.bind.annotation.XmlIDREF; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; @XmlType(name="dcel-half-edge") public class DcelHalfEdge<V, E, F> implements Serializable { private final static int ITERATION_LIMIT = 10000; private DcelHalfEdge<V, E, F> twin; private DcelVertex<V, E, F> origin; private DcelHalfEdge<V, E, F> next; private DcelFace<V, E, F> face; private E edge; public DcelHalfEdge() { this(null); } public DcelHalfEdge(E edge) { this.edge = edge; } @XmlID @XmlAttribute(name="id") public String getId() { return "dcel-" + (origin.getVertex() == null ? "null" : origin.getVertex().toString()) + "-" + (next.origin.getVertex() == null ? "null" : next.origin.getVertex().toString()); } protected void setId(String id) { } @XmlIDREF @XmlAttribute(name="twin-half-edge-id") public DcelHalfEdge<V, E, F> getTwin() { return twin; } @XmlIDREF @XmlAttribute(name="origin-vertex-id") public DcelVertex<V, E, F> getOrigin() { return origin; } @XmlIDREF @XmlAttribute(name="next-half-edge-id") public DcelHalfEdge<V, E, F> getNext() { return next; } @XmlIDREF @XmlAttribute(name="face-id") public DcelFace<V, E, F> getFace() { return face; } @XmlIDREF @XmlAttribute(name="data-id") public E getEdge() { return edge; } public void setEdge(E edge) { this.edge = edge; } public void setTwin(DcelHalfEdge<V, E, F> twin) { this.twin = twin; if (twin != null && twin.twin == null) { twin.twin = this; } } public void setNext(DcelHalfEdge<V, E, F> next) { this.next = next; } public void setFace(DcelFace<V, E, F> face) { this.face = face; if (face != null && face.getAdjacent() == null) { face.setAdjacent(this); } } public void setOrigin(DcelVertex<V, E, F> origin) { this.origin = origin; if (origin != null && origin.getLeaving() == null) { origin.setLeaving(this); } } public void invalidate() { setTwin(null); setNext(null); setFace(null); setOrigin(null); } @XmlTransient public DcelHalfEdge<V, E, F> getPrev() { for (DcelHalfEdge<V, E, F> halfEdge : origin.outHalfEdges()) { if (halfEdge.twin.next.equals(this)) { return halfEdge.twin; } } return null; } @XmlTransient public DcelHalfEdge<V, E, F> getPrevInternal() { for (DcelHalfEdge<V, E, F> halfEdge : prevEdges()) { if (!halfEdge.isEdgeBoundary()) { return halfEdge; } } return null; } @XmlTransient public DcelHalfEdge<V, E, F> getNextInternal() { for (DcelHalfEdge<V, E, F> halfEdge : edges()) { if (!halfEdge.isEdgeBoundary()) { return halfEdge; } } return null; } public Iterable<DcelHalfEdge<V, E, F>> edges() { return edges(this, true); } public Iterable<DcelHalfEdge<V, E, F>> prevEdges() { return edges(this, false); } public Iterable<DcelHalfEdge<V, E, F>> edges(final DcelHalfEdge<V, E, F> endEdge, final boolean forward) { final DcelHalfEdge<V, E, F> startEdge = this; return new Iterable<DcelHalfEdge<V, E, F>>() { @Override public Iterator<DcelHalfEdge<V, E, F>> iterator() { return new Iterator<DcelHalfEdge<V, E, F>>() { DcelHalfEdge<V, E, F> next = startEdge; int iteration = 0; @Override public boolean hasNext() { return (next != null); } @Override public DcelHalfEdge<V, E, F> next() { DcelHalfEdge<V, E, F> current = next; if (forward) { next = current.next; } else { next = current.getPrev(); } if (next == endEdge) { next = null; } if (++iteration > ITERATION_LIMIT) { throw new IndexOutOfBoundsException( "Iteration beyond limit, suggests corrupt structure."); } return current; } @Override public void remove() { throw new UnsupportedOperationException("Not supported yet."); } }; } }; } @XmlTransient public boolean isBoundary() { return face.isBoundary(); } @XmlTransient public boolean isEdgeBoundary() { return isBoundary() || twin.isBoundary(); } public void remove() { // Check if this edge separates two distinct faces if (face != twin.face) { // Remove the redundant face, merging the faces twin.face.setBoundary(face.isBoundary() || twin.face.isBoundary()); face.invalidate(); for (DcelHalfEdge<V, E, F> itr : edges()) { // Repoint edges to the conjoined face itr.setFace(twin.face); twin.face.setAdjacent(itr); } } else if (face.getAdjacent() == this) { // Hanging edge, ensure the face no longer references the removed edge face.setAdjacent(next.next); } // Reassign faces and vertices if necessary so they don't reference the deleted edge if (twin.face.getAdjacent() == twin) { // Note we set to next->next in case next = twin. twin.face.setAdjacent(twin.next.next); } if (twin.origin.getLeaving() == twin) { twin.origin.setLeaving(next); } if (origin.getLeaving() == this) { origin.setLeaving(twin.next); } // Handle disconnected vertices and faces if (twin.origin.getLeaving() == twin) { twin.origin.invalidate(); } if (origin.getLeaving() == this) { origin.invalidate(); } if (face.getAdjacent() == this) { face.invalidate(); } if (twin.face.getAdjacent() == twin) { twin.face.invalidate(); } // Reassign previous edges DcelHalfEdge<V, E, F> prev = getPrev(); if (prev != null) prev.setNext(twin.next); DcelHalfEdge<V, E, F> twinPrev = twin.getPrev(); if (twinPrev != null) twinPrev.setNext(next); // Finally invalidate these edges twin.invalidate(); invalidate(); } @Override public String toString() { return "E(" + ((edge == null) ? "NULL" : edge.toString()) + ") T(" + ((twin == null) ? "NULL" : twin.getId()) + ") N(" + ((next == null) ? "NULL" : next.getId()) + ") O(" + ((origin == null) ? "NULL" : origin.getIdString()) + ") F(" + ((face == null) ? "NULL" : face.getIdString()) + ")"; } @Override public boolean equals(Object rhs) { if (!(rhs instanceof DcelHalfEdge)) { return false; } { return ((DcelHalfEdge) rhs).origin.equals(origin) && ((DcelHalfEdge) rhs).next.origin.equals(next.origin); } } @Override public int hashCode() { int hash = 5; hash = 73 * hash + (this.next != null && this.next.origin != null ? this.next.origin.hashCode() : 0); hash = 73 * hash + (this.origin != null ? this.origin.hashCode() : 0); return hash; } }