Commit 43e77c48 authored by Benedikt Zoennchen's avatar Benedikt Zoennchen
Browse files

implementation of a very generic half-edge data structure

parent 0fb76bd7
package org.vadere.util.geometry.mesh;
import org.apache.commons.lang3.tuple.Triple;
import org.vadere.util.geometry.mesh.inter.IFace;
import org.vadere.util.geometry.shapes.IPoint;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VTriangle;
public class DAGElement<P extends IPoint> {
private Face<P> face;
import java.util.List;
public class DAGElement<P extends IPoint, F extends IFace<P>> {
private F face;
private Triple<P, P, P> vertices;
private VTriangle triangle;
public DAGElement(final Face<P> face, final Triple<P, P, P> vertices) {
public DAGElement(final F face, List<P> points) {
P p1 = points.get(0);
P p2 = points.get(1);
P p3 = points.get(2);
this.face = face;
this.vertices = Triple.of(p1, p2, p3);
this.triangle = new VTriangle(new VPoint(p1), new VPoint(p2), new VPoint(p3));
}
public DAGElement(final F face, final Triple<P, P, P> vertices) {
this.face = face;
this.vertices = vertices;
VPoint p1 = new VPoint(vertices.getLeft().getX(), vertices.getLeft().getY());
VPoint p2 = new VPoint(vertices.getMiddle().getX(), vertices.getMiddle().getY());
VPoint p3 = new VPoint(vertices.getRight().getX(), vertices.getRight().getY());
VPoint p1 = new VPoint(vertices.getLeft());
VPoint p2 = new VPoint(vertices.getMiddle());
VPoint p3 = new VPoint(vertices.getRight());
this.triangle = new VTriangle(p1, p2, p3);
}
public Face<P> getFace() {
public F getFace() {
return face;
}
......
package org.vadere.util.geometry.mesh;
import org.jetbrains.annotations.NotNull;
import org.vadere.util.geometry.GeometryUtils;
import org.vadere.util.geometry.shapes.IPoint;
import org.vadere.util.geometry.shapes.MLine;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VPolygon;
import org.vadere.util.geometry.shapes.VTriangle;
import java.awt.geom.Path2D;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* A Face is a region of a planar separation of the 2-D space, e.g. the region of a Polygon/Triangle and so on.
*
* @author Benedikt Zoennchen
* @param <P> the type of the coordinates the face uses.
*/
public class Face<P extends IPoint> implements Iterable<PHalfEdge<P>>, IFace<P> {
public static <P extends IPoint> Face<P> of(P x, P y, P z) {
Face superTriangle = new Face();
Face borderFace = new Face(true);
PHalfEdge xy = new PHalfEdge(y, superTriangle);
PHalfEdge yz = new PHalfEdge(z, superTriangle);
PHalfEdge zx = new PHalfEdge(x, superTriangle);
xy.setNext(yz);
yz.setNext(zx);
zx.setNext(xy);
PHalfEdge yx = new PHalfEdge(x, borderFace);
PHalfEdge zy = new PHalfEdge(y, borderFace);
PHalfEdge xz = new PHalfEdge(z, borderFace);
yx.setNext(xz);
xz.setNext(zy);
zy.setNext(yx);
xy.setTwin(yx);
yz.setTwin(zy);
zx.setTwin(xz);
superTriangle.setEdge(xy);
borderFace.setEdge(yx);
return superTriangle;
}
public static <P extends IPoint> Face<P> getBorder(Class<P> p) {
return new Face<>(true);
}
/**
* One of the half-edges bordering this face.
*/
private PHalfEdge<P> edge;
private boolean border;
private boolean destroyed = false;
/**
* Default constructor. To construct a face where you have already some half-edges
* bordering this face.
*
* @param edge one of the half-edges bordering this face.
*/
public Face(@NotNull final PHalfEdge<P> edge) {
this(edge, false);
}
public Face(@NotNull final PHalfEdge<P> edge, boolean border) {
this.border = border;
this.edge = edge;
}
/**
* This constructor can be used for constructing a new face without having
* constructed the bordering half-edges jet.
*/
public Face(boolean border) {
this.border = border;
}
public Face() {
this.border = false;
}
public boolean isBorder() {
return border;
}
public void destroy() {
setEdge(null);
destroyed = true;
}
public void toBorder() {
border = true;
}
/**
* Sets one of the half-edges bordering this face.
*
* @param edge half-edge bordering this face
*/
public void setEdge(final PHalfEdge<P> edge) {
this.edge = edge;
}
public PHalfEdge<P> getEdge() {
return edge;
}
public boolean isDestroyed() {
return destroyed;
}
/**
* Computes the area of this face.
*
* @return the area of this face
*/
public double getArea() {
return GeometryUtils.areaOfPolygon(getPoints());
}
/**
*
* @return
*/
public List<P> getPoints() {
return streamPoints().collect(Collectors.toList());
}
/**
* Returns true if and only if the point contained in this face.
*
* @param point the point which might be contained
* @return true if and only if the point contained in this face.
*/
public boolean contains(final P point) {
return toPolygon().contains(point);
}
/**
* Transforms this face into a Polygon object.
*
* @return the Polygon object defined by this face
*/
public VPolygon toPolygon() {
Path2D path2D = new Path2D.Double();
path2D.moveTo(edge.getPrevious().getEnd().getX(), edge.getPrevious().getEnd().getY());
for(PHalfEdge edge : this) {
path2D.lineTo(edge.getEnd().getX(), edge.getEnd().getY());
}
return new VPolygon(path2D);
}
/**
* Transforms this face into a triangle. Assumption: The face is a valid triangle.
*
* @throws IllegalArgumentException if the face does not define a valid triangle
* @return a triangle which is defined by this face
*/
public VTriangle toTriangle() {
List<PHalfEdge<P>> edges = getEdges();
if(edges.size() != 3) {
throw new IllegalArgumentException("this face is not a feasible triangle.");
}
else {
VPoint p1 = new VPoint(edges.get(0).getEnd().getX(), edges.get(0).getEnd().getY());
VPoint p2 = new VPoint(edges.get(1).getEnd().getX(), edges.get(1).getEnd().getY());
VPoint p3 = new VPoint(edges.get(2).getEnd().getX(), edges.get(2).getEnd().getY());
return new VTriangle(p1, p2, p3);
}
}
@Override
public Iterator<PHalfEdge<P>> iterator() {
return new HalfEdgeIterator();
}
public Stream<PHalfEdge<P>> stream () {
Iterable<PHalfEdge<P>> iterable = () -> iterator();
return StreamSupport.stream(iterable.spliterator(), false);
}
public List<PHalfEdge<P>> getEdges() {
return stream().collect(Collectors.toList());
}
public Stream<MLine<P>> streamLines() {
return stream().map(halfEdge -> new MLine(halfEdge.getPrevious().getEnd(), halfEdge.getEnd()));
}
public Stream<P> streamPoints() {
return stream().map(edge -> edge.getEnd());
}
@Override
public String toString() {
return stream().map(edge -> edge.getEnd().toString()).reduce("", (s1, s2) -> s1 + " " + s2);
}
private class HalfEdgeIterator implements Iterator<PHalfEdge<P>> {
private PHalfEdge<P> currentHalfEdge;
private boolean started = false;
private HalfEdgeIterator(){
this.currentHalfEdge = edge;
}
@Override
public boolean hasNext() {
return currentHalfEdge != null && (!started || !currentHalfEdge.equals(edge));
}
@Override
public PHalfEdge<P> next() {
started = true;
PHalfEdge result = currentHalfEdge;
currentHalfEdge = currentHalfEdge.getNext();
return result;
}
}
}
package org.vadere.util.geometry.mesh;
import org.apache.commons.collections.IteratorUtils;
import org.jetbrains.annotations.NotNull;
import org.vadere.util.geometry.shapes.IPoint;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VPolygon;
import java.awt.geom.Path2D;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
/**
* @author Benedikt Zoennchen
* @param <P>
*/
public interface IMesh<P extends IPoint, E extends IHalfEdge<P>, F extends IFace<P>> extends Iterable<F> {
E getNext(@NotNull E halfEdge);
E getPrev(@NotNull E halfEdge);
E getTwin(@NotNull E halfEdge);
F getFace(@NotNull E halfEdge);
E getEdge(@NotNull P vertex);
E getEdge(@NotNull F face);
P getVertex(@NotNull E halfEdge);
default F getTwinFace(@NotNull E halfEdge) {
return getFace(getTwin(halfEdge));
}
boolean isBoundary(@NotNull F face);
boolean isBoundary(@NotNull E halfEdge);
boolean isDestroyed(@NotNull F face);
void setTwin(@NotNull E halfEdge, @NotNull E twin);
void setNext(@NotNull E halfEdge, @NotNull E next);
void setPrev(@NotNull E halfEdge, @NotNull E prev);
void setFace(@NotNull E halfEdge, @NotNull F face);
void setEdge(@NotNull F face, @NotNull E edge);
void setEdge(@NotNull P vertex, @NotNull E edge);
void setVertex(@NotNull E halfEdge, @NotNull P vertex);
List<E> getEdges(@NotNull P vertex);
default List<E> getEdges(@NotNull F face) {
return IteratorUtils.toList(new EdgeIterator(this, face));
}
default List<F> getFaces(@NotNull E edge) { return IteratorUtils.toList(new NeighbourFaceIterator(this, edge)); }
default List<E> getNeighbours(@NotNull E edge) { return IteratorUtils.toList(new NeighbourIterator(this, edge)); }
default Iterable<E> getNeighbourIt(E edge) {
return () -> new NeighbourIterator(this, edge);
}
default Iterable<E> getEdgeIt(F face) {
return () -> new EdgeIterator(this, face);
}
default Iterable<F> getIncidentFacesIt(@NotNull E edge) { return () -> new NeighbourFaceIterator<>(this, edge); }
E createEdge(@NotNull P vertex);
E createEdge(@NotNull P vertex, @NotNull F face);
F createFace();
void destroyFace(@NotNull F face);
void destroyEdge(@NotNull E edge);
List<F> getFaces();
@Override
default Iterator<F> iterator() {
return getFaces().iterator();
}
default VPolygon toPolygon(F face) {
Path2D path2D = new Path2D.Double();
E edge = getEdge(face);
E prev = getPrev(edge);
path2D.moveTo(getVertex(prev).getX(), getVertex(prev).getY());
path2D.lineTo(getVertex(edge).getX(), getVertex(edge).getY());
while (!edge.equals(prev)) {
edge = getNext(edge);
P p = getVertex(edge);
path2D.lineTo(p.getX(), p.getY());
}
return new VPolygon(path2D);
}
default Optional<F> locate(final double x, final double y) {
for(F face : getFaces()) {
VPolygon polygon = toPolygon(face);
if(polygon.contains(new VPoint(x, y))) {
return Optional.of(face);
}
}
return Optional.empty();
}
}
package org.vadere.util.geometry.mesh;
import org.jetbrains.annotations.NotNull;
import org.vadere.util.geometry.mesh.inter.IFace;
import org.vadere.util.geometry.mesh.inter.IHalfEdge;
import org.vadere.util.geometry.mesh.inter.IMesh;
import org.vadere.util.geometry.shapes.IPoint;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VPolygon;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
......@@ -31,7 +36,7 @@ public interface IPolyConnectivity<P extends IPoint, E extends IHalfEdge<P>, F e
return Optional.empty();
}
default Optional<F> locate(final P point) {
default Optional<F> locate(final IPoint point) {
return locate(point.getX(), point.getY());
}
......@@ -46,7 +51,7 @@ public interface IPolyConnectivity<P extends IPoint, E extends IHalfEdge<P>, F e
default Optional<E> findEdge(P begin, P end) {
IMesh<P, E, F> mesh = getMesh();
return mesh.getNeighbours(mesh.getEdge(begin)).stream().filter(edge -> mesh.getPrev(edge).equals(end)).map(edge -> mesh.getTwin(edge)).findAny();
return mesh.getIncidentEdges(mesh.getEdge(begin)).stream().filter(edge -> mesh.getPrev(edge).equals(end)).map(edge -> mesh.getTwin(edge)).findAny();
}
default boolean isSimpleLink(E halfEdge) {
......@@ -120,4 +125,127 @@ public interface IPolyConnectivity<P extends IPoint, E extends IHalfEdge<P>, F e
getMesh().setFace(hold, face);
}
/**
* Removes a simple link.
*
* @param edge
* @return
*/
default F removeEdge(E edge) {
assert isSimpleLink(edge) && !getMesh().isDestroyed(edge);
E twin = getMesh().getTwin(edge);
F delFace = getMesh().getFace(edge);
F remFace = getMesh().getFace(twin);
if(getMesh().isBoundary(delFace)) {
F tmp = delFace;
delFace = remFace;
remFace = tmp;
}
assert !getMesh().isDestroyed(delFace);
E prevEdge = getMesh().getPrev(edge);
E prevTwin = getMesh().getPrev(twin);
E nextEdge = getMesh().getNext(edge);
E nextTwin = getMesh().getNext(twin);
getMesh().setNext(prevEdge, nextTwin);
getMesh().setNext(prevTwin, nextEdge);
/* adjust vertices, mb later
P eVertex = getMesh().getVertex(edge);
P tVertex = getMesh().getVertex(twin);
*/
if(getMesh().getEdge(remFace).equals(edge)) {
getMesh().setEdge(remFace, prevTwin);
}
else if(getMesh().getEdge(remFace).equals(twin)) {
getMesh().setEdge(remFace, prevEdge);
}
for(E halfEdge : getMesh().getEdgeIt(remFace)) {
getMesh().setFace(halfEdge, remFace);
}
getMesh().destroyEdge(edge);
getMesh().destroyEdge(twin);
getMesh().destroyFace(delFace);
return remFace;
}
default void removeFace(@NotNull F face, boolean deleteIsolatedVertices) {
assert !getMesh().isDestroyed(face);
getMesh().destroyFace(face);
List<E> delEdges = new ArrayList<>();
List<P> vertices = new ArrayList<>();
F boundary = getMesh().createFace(true);
for(E edge : getMesh().getEdgeIt(face)) {
getMesh().setFace(edge, boundary);
if(getMesh().isBoundary(getMesh().getTwin(edge))) {
delEdges.add(edge);
}
vertices.add(getMesh().getVertex(edge));
}
if(!delEdges.isEmpty()) {
E h0, h1, next0, next1, prev0, prev1;
P v0, v1;
for(E delEdge : delEdges) {
h0 = delEdge;
v0 = getMesh().getVertex(delEdge);
next0 = getMesh().getNext(h0);
prev0 = getMesh().getPrev(h0);
h1 = getMesh().getTwin(delEdge);
v1 = getMesh().getVertex(h1);
next1 = getMesh().getNext(h1);
prev1 = getMesh().getPrev(h1);
// adjust next and prev handles
getMesh().setNext(prev0, next1);
getMesh().setNext(prev1, next0);
// mark edge deleted if the mesh has a edge status
getMesh().destroyEdge(h0);
getMesh().destroyEdge(h1);
// TODO: delete isolated vertices?
for(P vertex : vertices) {
adjustVertex(vertex);
}
}
}
}
/**
* Returns a half-edge such that it is part of face1 and the twin of this half-edge
* is part of face2.
*
* @param face1 the first face
* @param face2 the second face that might be a neighbour of face1
* @return the half-edge of face1 such that its twin is part of face2
*/
default Optional<E> findTwins(final F face1, final F face2) {
for(E halfEdge1 : getMesh().getEdgeIt(face1)) {
for(E halfEdge2 : getMesh().getEdgeIt(face2)) {
if(getMesh().getTwin(halfEdge1).equals(halfEdge2)) {
return Optional.of(halfEdge1);
}
}
}
return Optional.empty();
}
}
package org.vadere.util.geometry.mesh;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.jetbrains.annotations.NotNull;
import org.vadere.util.geometry.mesh.inter.IFace;
import org.vadere.util.geometry.mesh.inter.IHalfEdge;
import org.vadere.util.geometry.mesh.inter.IMesh;
import org.vadere.util.geometry.shapes.IPoint;
import java.util.ArrayList;
......@@ -17,11 +18,9 @@ import java.util.List;
*/
public interface ITriConnectivity<P extends IPoint, E extends IHalfEdge<P>, F extends IFace<P>> extends IPolyConnectivity<P, E, F> {
default void splitTriangleEvent(F face, List<F> faces) {}
default void splitFaceEvent(F original, F... faces) {}
default void flipEvent(F f1, F f2) {}
void splitEdgeEvent(List<E> newEdges);
default void flipEdgeEvent(F f1, F f2) {}
boolean isIllegal(E edge);
......@@ -31,7 +30,7 @@ public interface ITriConnectivity<P extends IPoint, E extends IHalfEdge<P>, F ex
* @param p the split point
* @param halfEdge the half-edge which will be split
*/
default List<E> splitEdge(@NotNull P p, @NotNull E halfEdge) {
default List<E> splitEdge(@NotNull P p, @NotNull E halfEdge, boolean legalize) {
IMesh<P, E, F> mesh = getMesh();
List<E> newEdges = new ArrayList<>(4);
/*
......@@ -103,6 +102,8 @@ public interface ITriConnectivity<P extends IPoint, E extends IHalfEdge<P>, F ex
mesh.setNext(e0, h2);