Commit 622b0812 authored by Benedikt Zoennchen's avatar Benedikt Zoennchen

solve the nummerical problem in the triangulation algorithm

parent d7f1a0ac
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());
}
}
}
......@@ -6,6 +6,7 @@ import java.util.Set;
import java.util.stream.Stream;
public interface Triangulation<P extends IPoint> {
void compute();
Face<P> locate(final double x, final double y);
Face<P> locate(final IPoint point);
Stream<Face<P>> streamFaces();
......
......@@ -25,7 +25,6 @@ import javax.swing.*;
* 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.
*/
@Deprecated
public class BowyerWatson3 {
private List<VTriangle> triangles;
private Collection<VPoint> points;
......
......@@ -25,7 +25,6 @@ import java.util.stream.Stream;
* 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.
*/
@Deprecated
public class BowyerWatsonSlow<P extends IPoint> {
private List<Triple<P, P, P>> triangles;
private Collection<P> points;
......