The expiration time for new job artifacts in CI/CD pipelines is now 30 days (GitLab default). Previously generated artifacts in already completed jobs will not be affected by the change. The latest artifacts for all jobs in the latest successful pipelines will be kept. More information: https://gitlab.lrz.de/help/user/admin_area/settings/continuous_integration.html#default-artifacts-expiration

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);