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 3684752d authored by Benedikt Zoennchen's avatar Benedikt Zoennchen
Browse files

solve the nummerical problem in the triangulation algorithm

parent 236c496b
package org.vadere.util.delaunay;
import org.vadere.util.geometry.shapes.VCircle;
import org.vadere.util.geometry.shapes.VLine;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.geometry.shapes.VTriangle;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.swing.*;
/**
* @author Benedikt Zoennchen
*
* This class is for computing the DelaunayTriangulation using the BowyerWatson-Algorithm. In average the algorithm should perfom in O(n LOG(n)) but
* in degenerated cases its runtime can be in O(n^2) where n is the number of points.
*/
public class BowyerWatson3 {
private List<VTriangle> triangles;
private Collection<VPoint> points;
private List<VPoint> initPoints;
public BowyerWatson3(final Collection<VPoint> points) {
this.points = points;
}
public void execute() {
VPoint max = points.parallelStream().reduce(new VPoint(Integer.MIN_VALUE,Integer.MIN_VALUE), (a, b) -> new VPoint(Math.max(a.getX(), b.getX()), Math.max(a.getY(), b.getY())));
VPoint min = points.parallelStream().reduce(new VPoint(Integer.MAX_VALUE,Integer.MAX_VALUE), (a, b) -> new VPoint(Math.min(a.getX(), b.getX()), Math.min(a.getY(), b.getY())));
VRectangle bound = new VRectangle(min.getX(), min.getY(), max.getX()-min.getX(), max.getY()- min.getY());
init(bound);
points.stream().forEach(point -> handle(point));
cleanUp();
}
public List<VTriangle> getTriangles() {
return triangles;
}
public Set<VLine> getEdges() {
return triangles.parallelStream().flatMap(triangle -> Stream.of(triangle.getLines())).collect(Collectors.toSet());
}
private void init(final VRectangle bound) {
triangles = new ArrayList<>();
initPoints = new ArrayList<>();
VTriangle superTriangle = getSuperTriangle(bound);
triangles.add(superTriangle);
initPoints.addAll(superTriangle.getPoints());
}
private VTriangle getSuperTriangle(final VRectangle bound) {
double gap = 1.0;
double max = Math.max(bound.getWidth(), bound.getHeight());
VPoint p1 = new VPoint(bound.getX() - max - gap, bound.getY() - gap);
VPoint p2 = new VPoint(bound.getX() + 2 * max + gap, bound.getY() - gap);
VPoint p3 = new VPoint(bound.getX() + (max+2*gap)/2, bound.getY() + 2 * max+ gap);
return new VTriangle(p1, p2, p3);
}
private void handle(final VPoint point) {
HashSet<VLine> edges = new HashSet<>();
Map<Boolean, List<VTriangle>> partition = triangles.parallelStream().collect(Collectors.partitioningBy(triangle -> triangle.isInCircumscribedCycle(point)));
List<VTriangle> badTriangles = partition.get(true);
triangles = partition.get(false);
IntStream s;
HashSet<VLine> toRemove = new HashSet<>();
// duplicated edges
badTriangles.stream().flatMap(tri -> Stream.of(tri.getLines())).forEach(line -> {
if(!edges.add(line)) {
toRemove.add(line);
}
});
toRemove.stream().forEach(removeEdge -> edges.remove(removeEdge));
edges.stream().forEach(edge -> {
String[] id = edge.getIdentifier().split(":");
VPoint p1 = new VPoint(edge.getP1().getX(), edge.getP1().getY(), Integer.parseInt(id[0]));
VPoint p2 = new VPoint(edge.getP2().getX(), edge.getP2().getY(), Integer.parseInt(id[1]));
triangles.add(new VTriangle(p1, p2, point));
});
}
private void cleanUp() {
triangles = triangles.stream().filter(triangle -> !isTriangleConnectedToInitialPoints(triangle)).collect(Collectors.toList());
}
private boolean isTriangleConnectedToInitialPoints(final VTriangle triangle) {
return Stream.of(triangle.getLines()).anyMatch(edge -> {
VPoint p1 = new VPoint(edge.getP1().getX(), edge.getP1().getY());
VPoint p2 = new VPoint(edge.getP2().getX(), edge.getP2().getY());
return initPoints.stream().anyMatch(initPoint -> p1.equals(initPoint) || p2.equals(initPoint));
});
}
// TODO: the following code can be deleted, this is only for visual checks
public static void main(String[] args) {
// TODO Auto-generated method stub
int height = 1000;
int width = 1000;
int max = Math.max(height, width);
Set<VPoint> points = new HashSet<>();
/*points.add(new VPoint(20,20));
points.add(new VPoint(20,40));
points.add(new VPoint(75,53));
points.add(new VPoint(80,70));*/
Random r = new Random();
for(int i=0; i<10000; i++) {
VPoint point = new VPoint(width*r.nextDouble(), height*r.nextDouble());
points.add(point);
}
BowyerWatson3 bw = new BowyerWatson3(points);
bw.execute();
Set<VLine> edges = bw.getEdges();
JFrame window = new JFrame();
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setBounds(0, 0, max, max);
window.getContentPane().add(new Lines(edges, points, max));
window.setVisible(true);
}
private static class Lines extends JComponent{
private Set<VLine> edges;
private Set<VPoint> points;
private final int max;
public Lines(final Set<VLine> edges, final Set<VPoint> points, final int max){
this.edges = edges;
this.points = points;
this.max = max;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setBackground(Color.white);
g2.setStroke(new BasicStroke(1.0f));
g2.setColor(Color.gray);
edges.stream().forEach(edge -> {
Shape k = new VLine(edge.getP1().getX(), edge.getP1().getY(), edge.getP2().getX(), edge.getP2().getY());
g2.draw(k);
});
points.stream().forEach(point -> {
VCircle k = new VCircle(point.getX(), point.getY(), 1.0);
g2.draw(k);
});
}
}
public void setTriangles(List<VTriangle> triangles) {
this.triangles = triangles;
}
}
package org.vadere.util.delaunay;
import org.apache.commons.lang3.tuple.ImmutableTriple;
import org.apache.commons.lang3.tuple.Triple;
import org.vadere.util.geometry.shapes.IPoint;
import org.vadere.util.geometry.shapes.VCircle;
import org.vadere.util.geometry.shapes.VLine;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.geometry.shapes.VTriangle;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
* @author Benedikt Zoennchen
*
* This class is for computing the DelaunayTriangulation using the BowyerWatson-Algorithm. In average the algorithm should perfom in O(n log(n)) but
* in degenerated cases its runtime can be in O(n^2) where n is the number of points.
*/
public class BowyerWatsonSlow<P extends IPoint> {
private List<Triple<P, P, P>> triangles;
private Collection<P> points;
private List<P> initPoints;
private final BiFunction<Double, Double, P> pointConstructor;
public BowyerWatsonSlow(final Collection<P> points, final BiFunction<Double, Double, P> pointConstructor) {
this.points = points;
this.pointConstructor = pointConstructor;
}
public void execute() {
// P max = points.parallelStream().reduce(pointConstructor.apply(Double.MIN_VALUE, Double.MIN_VALUE), (a, b) -> pointConstructor.apply(Math.max(a.getX(), b.getX()), Math.max(a.getY(), b.getY())));
// P min = points.parallelStream().reduce(pointConstructor.apply(Double.MAX_VALUE, Double.MAX_VALUE), (a, b) -> pointConstructor.apply(Math.min(a.getX(), b.getX()), Math.min(a.getY(), b.getY())));
P max = points.parallelStream().reduce(pointConstructor.apply(Double.MIN_VALUE,Double.MIN_VALUE), (a, b) -> pointConstructor.apply(Math.max(a.getX(), b.getX()), Math.max(a.getY(), b.getY())));
P min = points.parallelStream().reduce(pointConstructor.apply(Double.MAX_VALUE,Double.MAX_VALUE), (a, b) -> pointConstructor.apply(Math.min(a.getX(), b.getX()), Math.min(a.getY(), b.getY())));
VRectangle bound = new VRectangle(min.getX(), min.getY(), max.getX()-min.getX(), max.getY()- min.getY());
init(bound);
points.stream().forEach(point -> handle(point));
cleanUp();
}
/*public List<Triple<P, P, P>> getTriangles() {
return triangles;
}*/
/*public void setTriangles(List<VTriangle> triangles) {
this.triangles = triangles;
}*/
public List<VTriangle> getTriangles() {
return triangles.stream().map(this::pointsToTriangle).collect(Collectors.toList());
}
public Set<VLine> getEdges() {
return triangles.parallelStream().map(triple -> pointsToTriangle(triple)).flatMap(triangle -> triangle.getLineStream()).collect(Collectors.toSet());
}
private void init(final VRectangle bound) {
triangles = new ArrayList<>();
initPoints = new ArrayList<>();
Triple<P, P, P> superTriangle = getSuperTriangle(bound);
triangles.add(superTriangle);
initPoints.add(superTriangle.getLeft());
initPoints.add(superTriangle.getMiddle());
initPoints.add(superTriangle.getRight());
}
private Triple<P, P, P> getSuperTriangle(final VRectangle bound) {
double gap = 1.0;
double max = Math.max(bound.getWidth(), bound.getHeight());
P p1 = pointConstructor.apply(bound.getX() - max - gap, bound.getY() - gap);
P p2 = pointConstructor.apply(bound.getX() + 2 * max + gap, bound.getY() - gap);
P p3 = pointConstructor.apply(bound.getX() + (max+2*gap)/2, bound.getY() + 2 * max+ gap);
return ImmutableTriple.of(p1, p2, p3);
}
private void handle(final P point) {
HashSet<Line> edges = new HashSet<>();
// This is way to expensive O(n) instead of O(log(n))
Map<Boolean, List<Triple<P, P, P>>> partition = triangles.parallelStream().collect(Collectors.partitioningBy(t -> pointsToTriangle(t).isInCircumscribedCycle(point)));
List<Triple<P, P, P>> badTriangles = partition.get(true);
triangles = partition.get(false);
IntStream s;
HashSet<Line> toRemove = new HashSet<>();
// duplicated edges
badTriangles.stream().flatMap(t -> getEdges(t).stream()).forEach(line -> {
if(!edges.add(line)) {
toRemove.add(line);
}
});
toRemove.stream().forEach(removeEdge -> edges.remove(removeEdge));
// identifier ?
edges.stream().forEach(edge -> triangles.add(Triple.of(edge.p1, edge.p2, point)));
}
private List<Line> getEdges(Triple<P, P, P> triangle) {
List<Line> list = new ArrayList<>();
list.add(new Line(triangle.getLeft(), triangle.getMiddle()));
list.add(new Line(triangle.getMiddle(), triangle.getRight()));
list.add(new Line(triangle.getRight(), triangle.getLeft()));
return list;
}
private void cleanUp() {
triangles = triangles.stream().filter(triangle -> !isTriangleConnectedToInitialPoints(triangle)).collect(Collectors.toList());
}
public void removeTriangleIf(final Predicate<Triple<P, P, P>> predicate) {
triangles.removeIf(predicate);
}
private boolean isTriangleConnectedToInitialPoints(final Triple<P, P, P> trianglePoints) {
return Stream.of(pointsToTriangle(trianglePoints).getLines()).anyMatch(edge -> {
VPoint p1 = new VPoint(edge.getP1().getX(), edge.getP1().getY());
VPoint p2 = new VPoint(edge.getP2().getX(), edge.getP2().getY());
return initPoints.stream().anyMatch(initPoint -> p1.equals(initPoint) || p2.equals(initPoint));
});
}
private VTriangle pointsToTriangle(Triple<P, P, P> points) {
return new VTriangle(
new VPoint(points.getLeft().getX(), points.getLeft().getY()),
new VPoint(points.getMiddle().getX(), points.getMiddle().getY()),
new VPoint(points.getRight().getX(), points.getRight().getY()));
}
private class Line {
final P p1;
final P p2;
private Line(P p1, P p2) {
this.p1 = p1;
this.p2 = p2;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Line line = (Line) o;
return (p1.equals(line.p1) && p2.equals(line.p2)) || (p2.equals(line.p1) && p1.equals(line.p2));
}
@Override
public int hashCode() {
return p1.hashCode() * p2.hashCode();
}
}
// TODO: the following code can be deleted, this is only for visual checks
public static void main(String[] args) {
// TODO Auto-generated method stub
int height = 1000;
int width = 1000;
int max = Math.max(height, width);
Set<VPoint> points = new HashSet<>();
/*points.add(new VPoint(20,20));
points.add(new VPoint(20,40));
points.add(new VPoint(75,53));
points.add(new VPoint(80,70));*/
Random r = new Random();
for(int i=0; i<100; i++) {
VPoint point = new VPoint(width*r.nextDouble(), height*r.nextDouble());
points.add(point);
}
BowyerWatsonSlow<VPoint> bw = new BowyerWatsonSlow<VPoint>(points, (x, y) -> new VPoint(x, y));
bw.execute();
Set<VLine> edges = bw.getEdges();
JFrame window = new JFrame();
window.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
window.setBounds(0, 0, max, max);
window.getContentPane().add(new Lines(edges, points, max));
window.setVisible(true);
}
private static class Lines extends JComponent{
private Set<VLine> edges;
private Set<VPoint> points;
private final int max;
public Lines(final Set<VLine> edges, final Set<VPoint> points, final int max){
this.edges = edges;
this.points = points;
this.max = max;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setBackground(Color.white);
g2.setStroke(new BasicStroke(1.0f));
g2.setColor(Color.gray);
edges.stream().forEach(edge -> {
Shape k = new VLine(edge.getP1().getX(), edge.getP1().getY(), edge.getP2().getX(), edge.getP2().getY());
g2.draw(k);
});
points.stream().forEach(point -> {
VCircle k = new VCircle(point.getX(), point.getY(), 1.0);
g2.draw(k);
});
}
}
}
package org.vadere.util.delaunay;
public class GuibasDAC {
}
package org.vadere.util.delaunay;
import org.vadere.util.geometry.data.Face;
import org.vadere.util.geometry.data.HalfEdge;
import org.vadere.util.geometry.shapes.VLine;
import org.vadere.util.geometry.shapes.VPoint;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class PointLocation<P extends VPoint> {
private final Collection<Face<P>> faces;
private final List<P> orderedPointList;
private final List<List<HalfEdge<P>>> halfeEdgesSegments;
private final List<List<P>> intersectionPointsInSegment;
private final BiFunction<Double, Double, P> pointConstructor;
private Comparator<P> pointComparatorX = (p1, p2) -> {
double dx = p1.getX() - p2.getX();
if(dx < 0) return -1;
else if(dx > 0) return 1;
else return 0;
};
private Comparator<P> pointComparatorY = (p1, p2) -> {
double dy = p1.getY() - p2.getY();
if(dy < 0) return -1;
else if(dy > 0) return 1;
else return 0;
};
private class BetweenTwoPoints implements Predicate<HalfEdge<P>> {
private P p1;
private P p2;
private BetweenTwoPoints(final P p1, final P p2) {
this.p1 = p1;
this.p2 = p2;
}
@Override
public boolean test(final HalfEdge<P> halfEdge) {
return (halfEdge.getEnd().getX() > p1.getX() && halfEdge.getPrevious().getEnd().getX() < p2.getX()) ||
(halfEdge.getEnd().getX() > p2.getX() && halfEdge.getPrevious().getEnd().getX() < p1.getX());
}
}
private class HalfEdgeComparator implements Comparator<HalfEdge<P>> {
private double x1;
private double x2;
private HalfEdgeComparator(final double x1, final double x2) {
this.x1 = x1;
this.x2 = x2;
}
@Override
public int compare(final HalfEdge<P> edge1, final HalfEdge<P> edge2) {
VLine line1 = edge1.toLine();
VLine line2 = edge2.toLine();
double slope1 = line1.slope();
double slope2 = line2.slope();
double yIntersection1 = line1.getY1() + (x1-line1.getX1()) * slope1;
double yIntersection2 = line2.getY1() + (x1 - line2.getX1()) * slope2;
if(yIntersection1 < yIntersection2) return -1;
else if(yIntersection1 > yIntersection2) return 1;
else return 0;
}
}
public PointLocation(final Collection<Face<P>> faces, final BiFunction<Double, Double, P> pointConstructor) {
this.faces = faces;
this.pointConstructor = pointConstructor;
//TODO distinct is maybe slow here
Set<P> pointSet = faces.stream()
.flatMap(face -> face.stream()).map(edge -> edge.getEnd())
.sorted(pointComparatorX).collect(Collectors.toSet());
orderedPointList = pointSet.stream().sorted(pointComparatorX).collect(Collectors.toList());
halfeEdgesSegments = new ArrayList<>(orderedPointList.size()-1);
intersectionPointsInSegment = new ArrayList<>(orderedPointList.size()-1);
for(int i = 0; i < orderedPointList.size() - 1; i++) {
P p1 = orderedPointList.get(i);
P p2 = orderedPointList.get(i+1);
List<HalfEdge<P>> halfEdges = faces.stream().flatMap(face -> face.stream()).filter(new BetweenTwoPoints(p1, p2))
.sorted(new HalfEdgeComparator(p1.getX(), p2.getX())).collect(Collectors.toList());
List<P> intersectionPoints = halfEdges.stream()
.map(hf -> hf.toLine())
.map(line -> intersectionWithX(p1.getX(), line)).collect(Collectors.toList());
halfeEdgesSegments.add(halfEdges);
intersectionPointsInSegment.add(intersectionPoints);
}
}
private P intersectionWithX(double x, VLine line) {
return pointConstructor.apply(x, (line.getY1() + (line.getX1()-x) * line.slope()));
}
public Optional<Face<P>> getFace(final P point) {
int index = Collections.binarySearch(orderedPointList, point, pointComparatorX);
int xSegmentIndex = (index >= 0) ? index : -index - 2;
if(xSegmentIndex < 0 || xSegmentIndex >= intersectionPointsInSegment.size()) {
return Optional.empty();
}
index = Collections.binarySearch(intersectionPointsInSegment.get(xSegmentIndex), point, pointComparatorY);
int ySegmentIndex = (index >= 0) ? index : -index - 2;
if(ySegmentIndex < 0 || ySegmentIndex >= halfeEdgesSegments.get(xSegmentIndex).size()) {
return Optional.empty();
}
HalfEdge<P> edge = halfeEdgesSegments.get(xSegmentIndex).get(ySegmentIndex);
if(edge.getFace().contains(point)) {
return Optional.of(edge.getFace());
}
else {
return Optional.of(edge.getTwin().getFace());
}
}
}