From 1f778dda45af8a50599717741c763af07bc063d5 Mon Sep 17 00:00:00 2001 From: Matthias Wegner Date: Thu, 11 Apr 2019 22:23:10 +0200 Subject: [PATCH] Extend example with colored voronoi-cells. --- .../alsclo/voronoi/colored/VoronoiCell.java | 143 ++++++++++++++++++ .../voronoi/colored/VoronoiExtended.java | 73 +++++++++ .../alsclo/voronoi/colored/VoronoiGraph.java | 83 ++++++++++ 3 files changed, 299 insertions(+) create mode 100644 src/test/java/de/alsclo/voronoi/colored/VoronoiCell.java create mode 100644 src/test/java/de/alsclo/voronoi/colored/VoronoiExtended.java create mode 100644 src/test/java/de/alsclo/voronoi/colored/VoronoiGraph.java diff --git a/src/test/java/de/alsclo/voronoi/colored/VoronoiCell.java b/src/test/java/de/alsclo/voronoi/colored/VoronoiCell.java new file mode 100644 index 0000000..90c536f --- /dev/null +++ b/src/test/java/de/alsclo/voronoi/colored/VoronoiCell.java @@ -0,0 +1,143 @@ + +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import de.alsclo.voronoi.graph.Edge; +import de.alsclo.voronoi.graph.Point; + +public class VoronoiCell implements Shape { + private GeneralPath path = new GeneralPath(); + + private ArrayList edges; + + private List points = new ArrayList(); + + private Point center; + + public ArrayList getEdges() { + return edges; + } + + public List getPoints() { + return points; + } + + public Point getCenter() { + return center; + } + + public VoronoiCell(Point center, ArrayList edges) { + + this.center = center; + + for (Edge edge : edges) { + Point a = edge.getA().getLocation(); + Point b = edge.getB().getLocation(); + + if (!points.contains(a)) { + points.add(a); + } + if (!points.contains(b)) { + points.add(b); + } + } + + this.edges = edges; + + if (points.size()==0) { + return; + } + + points = sortVertices(points); + + if (points.size() > 0) { + path.moveTo(points.get(0).x, points.get(0).y); + for (int i = 1; i < points.size(); i++) { + Point p = points.get(i); + path.lineTo(p.x, p.y); + } + path.closePath(); + } + + } + + private Point findCentroid(List points) { + + int x = 0; + int y = 0; + for (Point p : points) { + x += p.x; + y += p.y; + } + int cx = x / points.size(); + int cy = y / points.size(); + return new Point(cx, cy); + } + + private List sortVertices(List points) { + Point center = findCentroid(points); + Collections.sort(points, (a, b) -> { + double a1 = (Math.toDegrees(Math.atan2(a.x - center.x, a.y - center.y)) + 360) % 360; + double a2 = (Math.toDegrees(Math.atan2(b.x - center.x, b.y - center.y)) + 360) % 360; + return (int) (a1 - a2); + }); + return points; + } + + @Override + public java.awt.Rectangle getBounds() { + return path.getBounds(); + } + + @Override + public Rectangle2D getBounds2D() { + return path.getBounds2D(); + } + + @Override + public boolean contains(double x, double y) { + return path.contains(x, y); + } + + @Override + public boolean contains(Point2D p) { + return path.contains(p); + } + + @Override + public boolean intersects(double x, double y, double w, double h) { + return path.intersects(x, y, w, h); + } + + @Override + public boolean intersects(Rectangle2D r) { + return path.intersects(r); + } + + @Override + public boolean contains(double x, double y, double w, double h) { + return path.contains(x, y, w, h); + } + + @Override + public boolean contains(Rectangle2D r) { + return path.contains(r); + } + + @Override + public PathIterator getPathIterator(AffineTransform at) { + return path.getPathIterator(at); + } + + @Override + public PathIterator getPathIterator(AffineTransform at, double flatness) { + return path.getPathIterator(at, flatness); + } +} \ No newline at end of file diff --git a/src/test/java/de/alsclo/voronoi/colored/VoronoiExtended.java b/src/test/java/de/alsclo/voronoi/colored/VoronoiExtended.java new file mode 100644 index 0000000..886c291 --- /dev/null +++ b/src/test/java/de/alsclo/voronoi/colored/VoronoiExtended.java @@ -0,0 +1,73 @@ +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.alsclo.voronoi.Voronoi; +import de.alsclo.voronoi.graph.Edge; +import de.alsclo.voronoi.graph.Point; +import de.alsclo.voronoi.graph.Vertex; + +public class VoronoiExtended extends Voronoi { + + @Override + public VoronoiExtended relax() { + Map> edges = new HashMap<>(); + getGraph().getSitePoints().forEach(p -> edges.put(p, new HashSet<>())); + getGraph().edgeStream().forEach(e -> { + edges.get(e.getSite1()).add(e); + edges.get(e.getSite2()).add(e); + }); + List newPoints = getGraph().getSitePoints().stream().map(site -> { + Set vertices = Stream + .concat(edges.get(site).stream().map(Edge::getA), edges.get(site).stream().map(Edge::getB)) + .collect(Collectors.toSet()); + if (vertices.isEmpty() || vertices.contains(null)) { + return site; + } else { + double avgX = vertices.stream().mapToDouble(v -> v.getLocation().x).average().getAsDouble(); + double avgY = vertices.stream().mapToDouble(v -> v.getLocation().y).average().getAsDouble(); + return new Point(avgX, avgY); + } + }).collect(Collectors.toList()); + return new VoronoiExtended(newPoints); + } + + private ArrayList cells = new ArrayList(); + + public ArrayList getCells() { + return cells; + } + + public VoronoiExtended(Collection points) { + super(points); + + for (Point site : this.getGraph().getSitePoints()) { + + ArrayList edges = new ArrayList(); + this.getGraph().edgeStream().filter(e -> e.getA() != null && e.getB() != null).forEach(e -> { + Edge edge = null; + if (site.x == e.getSite1().x && site.y == e.getSite1().y) { + edge = e; + } + if (site.x == e.getSite2().x && site.y == e.getSite2().y) { + edge = e; + } + if (edge != null) { + edges.add(edge); + } + + }); + + VoronoiCell t = new VoronoiCell(site, edges); + cells.add(t); + + } + } + +} diff --git a/src/test/java/de/alsclo/voronoi/colored/VoronoiGraph.java b/src/test/java/de/alsclo/voronoi/colored/VoronoiGraph.java new file mode 100644 index 0000000..a3a51c5 --- /dev/null +++ b/src/test/java/de/alsclo/voronoi/colored/VoronoiGraph.java @@ -0,0 +1,83 @@ +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.swing.JFrame; + +import de.alsclo.voronoi.graph.Edge; +import de.alsclo.voronoi.graph.Point; + +public class VoronoiGraph extends JFrame { + + private static final double OFFSET = 0.0; + + private static final int size = 1024; + + private static final double POINT_SIZE = 20.0; + private final VoronoiExtended diagram; + + public VoronoiGraph(VoronoiExtended diagram) { + this.diagram = diagram; + } + + public void paint(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + + ArrayList cells = this.diagram.getCells(); + for (VoronoiCell cell : cells) { + Random randomGenerator = new Random(); + int red = randomGenerator.nextInt(256); + int green = randomGenerator.nextInt(256); + int blue = randomGenerator.nextInt(256); + + Color randomColour = new Color(red,green,blue); + g2.setStroke(new BasicStroke()); + g2.setPaint(randomColour); + g2.fill(cell); + + List edges = cell.getEdges(); + for (Edge edge : edges) { + g2.setPaint(Color.LIGHT_GRAY); + g2.drawLine((int) edge.getSite1().x, (int) edge.getSite1().y + (int) OFFSET, (int) edge.getSite2().x , (int) edge.getSite2().y + (int) OFFSET); + } + + } + + + for (Point site : diagram.getGraph().getSitePoints()) { + g2.setStroke(new BasicStroke()); + g2.setPaint(Color.BLACK); + + g2.fillOval((int) Math.round(site.x - POINT_SIZE / 2), + (int) Math.round(site.y - POINT_SIZE / 2) + (int) OFFSET, (int) POINT_SIZE, (int) POINT_SIZE); + // g2.drawString(String.format("%d,%d", (int)site.x, (int)site.y), (int) site.x, + // size - (int)site.y + 32); + } + + g2.setStroke(new BasicStroke()); + g2.setPaint(Color.BLACK); + + diagram.getGraph().edgeStream().filter(e -> e.getA() != null && e.getB() != null).forEach(e -> { + Point a = e.getA().getLocation(); + Point b = e.getB().getLocation(); + + g2.drawLine((int) a.x, (int) a.y + (int) OFFSET, (int) b.x, (int) b.y + (int) OFFSET); + }); + } + + public static void main(String[] args) { + Random r = new Random(9235563856L); + Stream gen = Stream.generate(() -> new Point(r.nextDouble() * size, r.nextDouble() * size)); + VoronoiExtended diagram = new VoronoiExtended(gen.limit(20).collect(Collectors.toList())).relax().relax(); + VoronoiGraph frame = new VoronoiGraph(diagram); + frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + frame.setSize(size, size + (int) OFFSET); + frame.setVisible(true); + } +}