├── .gitignore ├── .travis.yml ├── License.md ├── README.md ├── pom.xml └── src ├── main └── java │ └── de │ └── alsclo │ └── voronoi │ ├── Math.java │ ├── Voronoi.java │ ├── beachline │ ├── BeachNode.java │ ├── Beachline.java │ ├── InnerBeachNode.java │ ├── InsertionResult.java │ └── LeafBeachNode.java │ ├── event │ ├── Event.java │ ├── SiteEvent.java │ └── VertexEvent.java │ └── graph │ ├── BisectorMap.java │ ├── Edge.java │ ├── Graph.java │ ├── Point.java │ └── Vertex.java └── test └── java └── de └── alsclo └── voronoi ├── VoronoiTest.java ├── beachline └── BeachlineTests.java └── gui └── RenderVoronoi.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Windows template 3 | # Windows image file caches 4 | Thumbs.db 5 | ehthumbs.db 6 | 7 | # Folder config file 8 | Desktop.ini 9 | 10 | # Recycle Bin used on file shares 11 | $RECYCLE.BIN/ 12 | 13 | # Windows Installer files 14 | *.cab 15 | *.msi 16 | *.msm 17 | *.msp 18 | 19 | # Windows shortcuts 20 | *.lnk 21 | ### macOS template 22 | *.DS_Store 23 | .AppleDouble 24 | .LSOverride 25 | 26 | # Icon must end with two \r 27 | Icon 28 | 29 | 30 | # Thumbnails 31 | ._* 32 | 33 | # Files that might appear in the root of a volume 34 | .DocumentRevisions-V100 35 | .fseventsd 36 | .Spotlight-V100 37 | .TemporaryItems 38 | .Trashes 39 | .VolumeIcon.icns 40 | .com.apple.timemachine.donotpresent 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | ### Maven template 49 | target/ 50 | pom.xml.tag 51 | pom.xml.releaseBackup 52 | pom.xml.versionsBackup 53 | pom.xml.next 54 | release.properties 55 | dependency-reduced-pom.xml 56 | buildNumber.properties 57 | .mvn/timing.properties 58 | 59 | # Exclude maven wrapper 60 | !/.mvn/wrapper/maven-wrapper.jar 61 | ### Linux template 62 | *~ 63 | 64 | # temporary files which can be created if a process still has a handle open of a deleted file 65 | .fuse_hidden* 66 | 67 | # KDE directory preferences 68 | .directory 69 | 70 | # Linux trash folder which might appear on any partition or disk 71 | .Trash-* 72 | 73 | # .nfs files are created when an open file is removed but is still being accessed 74 | .nfs* 75 | ### JetBrains template 76 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 77 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 78 | 79 | # User-specific stuff: 80 | .idea/workspace.xml 81 | .idea/tasks.xml 82 | 83 | # Sensitive or high-churn files: 84 | .idea/dataSources/ 85 | .idea/dataSources.ids 86 | .idea/dataSources.xml 87 | .idea/dataSources.local.xml 88 | .idea/sqlDataSources.xml 89 | .idea/dynamic.xml 90 | .idea/uiDesigner.xml 91 | 92 | # Gradle: 93 | .idea/gradle.xml 94 | .idea/libraries 95 | 96 | # Mongo Explorer plugin: 97 | .idea/mongoSettings.xml 98 | 99 | ## File-based project format: 100 | *.iws 101 | 102 | ## Plugin-specific files: 103 | 104 | # IntelliJ 105 | /out/ 106 | 107 | # mpeltonen/sbt-idea plugin 108 | .idea_modules/ 109 | 110 | # JIRA plugin 111 | atlassian-ide-plugin.xml 112 | 113 | # Crashlytics plugin (for Android Studio and IntelliJ) 114 | com_crashlytics_export_strings.xml 115 | crashlytics.properties 116 | crashlytics-build.properties 117 | fabric.properties 118 | .idea 119 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Schlosser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # voronoi-java [![License](http://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)][License] [![Build Status](https://travis-ci.org/aschlosser/voronoi-java.svg?branch=master)](https://travis-ci.org/aschlosser/voronoi-java) 2 | A lightweight java library for generating 2D Voronoi diagrams using Fortune's Algorithm 3 | 4 | ## Usage 5 | voronoi-java is available in the central maven repository: 6 | ``` 7 | 8 | de.alsclo 9 | voronoi-java 10 | 1.0 11 | 12 | ``` 13 | To get the latest version you can just clone the repository and install it into your local maven repository (see [Building from Source](#building-from-source)). 14 | 15 | ### Getting started 16 | Use a code snippet like this: 17 | ``` 18 | Collection points = ... 19 | Voronoi voronoi = new Voronoi(points); 20 | voronoi.getGraph(); 21 | ``` 22 | 23 | ## Source Code 24 | The latest source can be found here on [GitHub](https://github.com/aschlosser/voronoi-java). To clone the project: 25 | 26 | git clone git://github.com/aschlosser/voronoi-java.git 27 | 28 | Or download the latest [archive](https://github.com/aschlosser/voronoi-java/archive/master.zip). 29 | 30 | ## Building from Source 31 | This project can be built with the _latest_ [Java Development Kit](http://oracle.com/technetwork/java/javase/downloads) and [Maven](https://maven.apache.org/). The command `mvn package` will build the project and will put the compiled JAR in `target`, and `mvn install` will copy it to your local Maven repository. 32 | 33 | ## Contributing 34 | Your help is welcome! Just open a pull request with your changes. 35 | 36 | ## License 37 | voronoi-java is licensed under the [MIT License][License]. Basically, you can do as you please as long as you include the original copyright notice. Please see the `License.md` file for details. 38 | 39 | ## Credits 40 | Most of the code is derived from the desciptions included in the book 'Computational Geometry: Algorithms and Applications' 41 | by Mark de Berg, Otfried Cheong, Marc van Kreveld and Mark Overmars (ISBN-13: 978-3540779735) 42 | 43 | [License]: https://choosealicense.com/licenses/mit/ 44 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | de.alsclo 6 | voronoi-java 7 | A lightweight java library for generating 2D Voronoi diagrams using Fortune's Algorithm 8 | jar 9 | 1.0 10 | voronoi-java 11 | https://github.com/aschlosser/voronoi-java 12 | 13 | 14 | 15 | MIT License 16 | https://github.com/aschlosser/voronoi-java/blob/master/License.md 17 | repo 18 | 19 | 20 | 21 | 22 | https://github.com/aschlosser/voronoi-java 23 | scm:git:git://github.com/aschlosser/voronoi-java.git 24 | scm:git:git@github.com:aschlosser/voronoi-java.git 25 | 26 | 27 | 28 | 29 | aschlosser25@gmail.com 30 | Alex Schlosser 31 | https://github.com/aschlosser 32 | alsclo 33 | 34 | 35 | 36 | 37 | 1.8 38 | 1.8 39 | UTF-8 40 | 41 | 42 | 43 | 44 | junit 45 | junit 46 | 4.12 47 | test 48 | 49 | 50 | org.projectlombok 51 | lombok 52 | 1.16.12 53 | provided 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 3.1 63 | 64 | 1.8 65 | 1.8 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-source-plugin 71 | 3.0.1 72 | 73 | 74 | attach-sources 75 | 76 | jar-no-fork 77 | 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-javadoc-plugin 84 | 2.10.4 85 | 86 | 87 | attach-javadocs 88 | 89 | jar 90 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-gpg-plugin 97 | 1.6 98 | 99 | 100 | sign-artifacts 101 | deploy 102 | 103 | sign 104 | 105 | 106 | 107 | 108 | 109 | org.sonatype.plugins 110 | nexus-staging-maven-plugin 111 | 1.6.7 112 | true 113 | 114 | ossrh 115 | https://oss.sonatype.org/ 116 | false 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | ossrh 125 | https://oss.sonatype.org/content/repositories/snapshots 126 | 127 | 128 | ossrh 129 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/Math.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi; 2 | 3 | public class Math { 4 | 5 | public static final double PRECISION = 100000.0; 6 | 7 | public static final double EPSILON = 1.0 / PRECISION; 8 | 9 | public static double sq(double o) { 10 | return o * o; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/Voronoi.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi; 2 | 3 | import de.alsclo.voronoi.beachline.Beachline; 4 | import de.alsclo.voronoi.event.Event; 5 | import de.alsclo.voronoi.event.SiteEvent; 6 | import de.alsclo.voronoi.graph.Edge; 7 | import de.alsclo.voronoi.graph.Graph; 8 | import de.alsclo.voronoi.graph.Point; 9 | import de.alsclo.voronoi.graph.Vertex; 10 | import lombok.Getter; 11 | import lombok.val; 12 | 13 | import java.util.*; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.Stream; 16 | 17 | /** 18 | * The main class and entry point of voronoi-java. Constructing a new instance of this class will generate the diagram in the form of a {@link de.alsclo.voronoi.graph.Graph}. 19 | * 20 | */ 21 | public class Voronoi { 22 | 23 | @Getter 24 | private final Graph graph = new Graph(); 25 | 26 | 27 | /** 28 | * Builds an unbounded voronoi diagram based on the given site points. 29 | * 30 | * @param points the site points 31 | */ 32 | public Voronoi(Collection points) { 33 | val queue = new PriorityQueue(); 34 | points.stream().map(SiteEvent::new).forEach(queue::offer); 35 | points.forEach(graph::addSite); 36 | 37 | val beachline = new Beachline(); 38 | double sweep = Double.MAX_VALUE; 39 | while (!queue.isEmpty()) { 40 | val e = queue.peek(); 41 | assert e.getPoint().y <= sweep; 42 | e.handle(queue, beachline, graph); 43 | queue.remove(e); 44 | sweep = e.getPoint().y; 45 | } 46 | } 47 | 48 | 49 | /** 50 | * Applies the bounding box with the specified coordinates to a copy of this voronoi diagram. If this voronoi diagram is already unbounded an identical copy is returned. 51 | * 52 | * @param x the x coordinate of the bounding box 53 | * @param y the y coordinate of the bounding box 54 | * @param width the width of the bounding box 55 | * @param height the height of the bounding box 56 | * 57 | * @return a copy of this voronoi digram bounded by the given bounding box 58 | * @throws IllegalArgumentException if any site point of this diagram lies outside the given bounding box 59 | */ 60 | public Voronoi applyBoundingBox(double x, double y, double width, double height) { 61 | getGraph().getSitePoints().stream() 62 | .filter(p -> p.x < x || p.x > x + width || p.y < y || p.y > y + height).findAny().ifPresent(p -> { 63 | throw new IllegalArgumentException("Site " + p + " lies outside the bounding box."); 64 | }); 65 | throw new UnsupportedOperationException("Not implemented.");//TODO 66 | } 67 | 68 | /** 69 | * Computes a lloyd relaxation of this voronoi diagram. The original diagram remains unchanged. 70 | * @see Lloyd's algorithm on wikipedia 71 | * 72 | * @return a new voronoi diagram representing the lloyd relaxation of this one 73 | */ 74 | public Voronoi relax() { 75 | Map> edges = new HashMap<>(); 76 | graph.getSitePoints().forEach(p -> edges.put(p, new HashSet<>())); 77 | graph.edgeStream().forEach(e -> { 78 | edges.get(e.getSite1()).add(e); 79 | edges.get(e.getSite2()).add(e); 80 | }); 81 | List newPoints = graph.getSitePoints().stream().map(site -> { 82 | Set vertices = Stream.concat(edges.get(site).stream().map(Edge::getA), edges.get(site).stream().map(Edge::getB)).collect(Collectors.toSet()); 83 | if (vertices.isEmpty() || vertices.contains(null)) { 84 | return site; 85 | } else { 86 | double avgX = vertices.stream().mapToDouble(v -> v.getLocation().x).average().getAsDouble(); 87 | double avgY = vertices.stream().mapToDouble(v -> v.getLocation().y).average().getAsDouble(); 88 | return new Point(avgX, avgY); 89 | } 90 | }).collect(Collectors.toList()); 91 | return new Voronoi(newPoints); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/beachline/BeachNode.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.beachline; 2 | 3 | import de.alsclo.voronoi.graph.Point; 4 | 5 | public abstract class BeachNode { 6 | 7 | private InnerBeachNode parent; 8 | 9 | public abstract InsertionResult insertArc(Point newSite); 10 | 11 | public abstract LeafBeachNode getLeftmostLeaf(); 12 | 13 | public abstract LeafBeachNode getRightmostLeaf(); 14 | 15 | void replaceBy(BeachNode n) { 16 | if (getParent() != null) { 17 | if (getParent().getLeftChild() == this) { 18 | getParent().setLeftChild(n); 19 | } else { 20 | getParent().setRightChild(n); 21 | } 22 | } 23 | } 24 | 25 | public InnerBeachNode getParent() { 26 | return parent; 27 | } 28 | 29 | public void setParent(InnerBeachNode parent) { 30 | this.parent = parent; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/beachline/Beachline.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.beachline; 2 | 3 | 4 | import de.alsclo.voronoi.graph.Point; 5 | 6 | import java.util.Optional; 7 | import java.util.stream.Stream; 8 | 9 | public class Beachline { 10 | 11 | private final InnerBeachNode rootContainer = new InnerBeachNode(); 12 | 13 | public InsertionResult insertArc(Point newSite) { 14 | BeachNode root = getRoot(); 15 | if (root != null) { 16 | return root.insertArc(newSite); 17 | } else { 18 | LeafBeachNode l = new LeafBeachNode(newSite); 19 | setRoot(l); 20 | return new InsertionResult(Optional.empty(), l); 21 | } 22 | } 23 | 24 | BeachNode getRoot() { 25 | return rootContainer.getLeftChild(); 26 | } 27 | 28 | void setRoot(BeachNode n) { 29 | rootContainer.setLeftChild(n); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | StringBuilder sb = new StringBuilder(); 35 | sb.append("Beachline("); 36 | if (getRoot() != null) { 37 | Optional current = Optional.of(getRoot().getLeftmostLeaf()); 38 | while (current.isPresent()) { 39 | sb.append(current.get().getSite()).append(','); 40 | current = current.flatMap(LeafBeachNode::getRightNeighbor); 41 | } 42 | } 43 | sb.append(")"); 44 | return sb.toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/beachline/InnerBeachNode.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.beachline; 2 | 3 | import de.alsclo.voronoi.graph.Point; 4 | import lombok.EqualsAndHashCode; 5 | import lombok.Getter; 6 | import lombok.ToString; 7 | 8 | import java.util.stream.Stream; 9 | 10 | import static de.alsclo.voronoi.Math.sq; 11 | import static java.lang.Math.sqrt; 12 | 13 | @Getter 14 | @ToString 15 | @EqualsAndHashCode(callSuper = false) 16 | public class InnerBeachNode extends BeachNode { 17 | 18 | private BeachNode leftChild; 19 | private BeachNode rightChild; 20 | 21 | InnerBeachNode() { 22 | 23 | } 24 | 25 | public InnerBeachNode(BeachNode leftChild, BeachNode rightChild) { 26 | setLeftChild(leftChild); 27 | setRightChild(rightChild); 28 | } 29 | 30 | @Override 31 | public InsertionResult insertArc(Point newSite) { 32 | // Find leafs represented by this inner node 33 | Point l = leftChild.getRightmostLeaf().getSite(); 34 | Point r = rightChild.getLeftmostLeaf().getSite(); 35 | 36 | // Transform coordinate to local coords 37 | double lxOld = l.x; 38 | r = new Point(r.x - l.x, r.y - newSite.y); 39 | l = new Point(0, l.y - newSite.y); 40 | 41 | // Compute intersection of parabolas 42 | double x; 43 | if (Double.compare(l.y, r.y) == 0) { 44 | x = r.x / 2.0; 45 | } else if (l.y == 0.0) { 46 | x = l.x; 47 | } else if (r.y == 0.0) { 48 | x = r.x; 49 | } else { 50 | x = (l.y * r.x - sqrt(l.y * r.y * (sq(l.y - r.y) + sq(r.x)))) / (l.y - r.y); 51 | } 52 | 53 | x += lxOld; 54 | 55 | return newSite.x < x ? leftChild.insertArc(newSite) : rightChild.insertArc(newSite); 56 | } 57 | 58 | @Override 59 | public LeafBeachNode getLeftmostLeaf() { 60 | return leftChild.getLeftmostLeaf(); 61 | } 62 | 63 | @Override 64 | public LeafBeachNode getRightmostLeaf() { 65 | return rightChild.getRightmostLeaf(); 66 | } 67 | 68 | void setLeftChild(BeachNode leftChild) { 69 | this.leftChild = leftChild; 70 | leftChild.setParent(this); 71 | } 72 | 73 | void setRightChild(BeachNode rightChild) { 74 | this.rightChild = rightChild; 75 | rightChild.setParent(this); 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/beachline/InsertionResult.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.beachline; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | 5 | import java.util.Optional; 6 | 7 | @RequiredArgsConstructor 8 | public class InsertionResult { 9 | public final Optional splitLeaf; 10 | public final LeafBeachNode newLeaf; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/beachline/LeafBeachNode.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.beachline; 2 | 3 | import de.alsclo.voronoi.event.Event; 4 | import de.alsclo.voronoi.event.VertexEvent; 5 | import de.alsclo.voronoi.graph.Point; 6 | import lombok.EqualsAndHashCode; 7 | 8 | import java.util.Collections; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.function.Consumer; 13 | 14 | @EqualsAndHashCode(callSuper = false) 15 | public class LeafBeachNode extends BeachNode { 16 | 17 | private final Point site; 18 | 19 | private final List subscribedEvents = new LinkedList<>(); 20 | 21 | LeafBeachNode(Point site) { 22 | this.site = site; 23 | } 24 | 25 | private LeafBeachNode copy() { 26 | return new LeafBeachNode(site); 27 | } 28 | 29 | public Point getSite() { 30 | return site; 31 | } 32 | 33 | @Override 34 | public InsertionResult insertArc(Point newSite) { 35 | LeafBeachNode newLeaf = new LeafBeachNode(newSite); 36 | if (newSite.y == site.y) { 37 | if (newSite.x < site.x) { 38 | replaceBy(new InnerBeachNode(newLeaf, copy())); 39 | } else { 40 | replaceBy(new InnerBeachNode(copy(), newLeaf)); 41 | } 42 | } else { 43 | if (newSite.x < site.x) { 44 | replaceBy(new InnerBeachNode(new InnerBeachNode(copy(), newLeaf), copy())); 45 | } else { 46 | replaceBy(new InnerBeachNode(copy(), new InnerBeachNode(newLeaf, copy()))); 47 | } 48 | } 49 | setParent(null); // Disconnect this node from the tree 50 | return new InsertionResult(Optional.of(this), newLeaf); 51 | } 52 | 53 | public void remove() { 54 | InnerBeachNode parent = getParent(); 55 | BeachNode sibling = parent.getLeftChild() == this ? parent.getRightChild() : parent.getLeftChild(); 56 | parent.replaceBy(sibling); 57 | setParent(null); // Disconnect this node from the tree 58 | } 59 | 60 | @Override 61 | public LeafBeachNode getLeftmostLeaf() { 62 | return this; 63 | } 64 | 65 | @Override 66 | public LeafBeachNode getRightmostLeaf() { 67 | return this; 68 | } 69 | 70 | public Optional getLeftNeighbor() { 71 | InnerBeachNode current = getParent(); 72 | BeachNode child = this; 73 | if (current != null) { 74 | while (current.getParent() != null) { 75 | if (current.getRightChild() == child) { 76 | return Optional.of(current.getLeftChild().getRightmostLeaf()); 77 | } else { 78 | child = current; 79 | current = current.getParent(); 80 | } 81 | } 82 | } 83 | return Optional.empty(); 84 | } 85 | 86 | public Optional getRightNeighbor() { 87 | InnerBeachNode current = getParent(); 88 | BeachNode child = this; 89 | if (current != null) { 90 | while (current.getParent() != null) { 91 | if (current.getLeftChild() == child) { 92 | return Optional.of(current.getRightChild().getLeftmostLeaf()); 93 | } else { 94 | child = current; 95 | current = current.getParent(); 96 | } 97 | } 98 | } 99 | return Optional.empty(); 100 | } 101 | 102 | private static Optional buildEvent(LeafBeachNode center) { 103 | return center.getLeftNeighbor().flatMap(l -> center.getRightNeighbor().flatMap(r -> VertexEvent.build(l, center, r))); 104 | } 105 | 106 | 107 | public void addCircleEvents(Consumer q, double sweepY) { 108 | // l2 -> l1 -> leaf <- r1 <- r2 109 | getLeftNeighbor().flatMap(LeafBeachNode::getLeftNeighbor).flatMap(LeafBeachNode::buildEvent).ifPresent(q); 110 | getLeftNeighbor().flatMap(LeafBeachNode::buildEvent).ifPresent(q); 111 | buildEvent(this).ifPresent(q); 112 | getRightNeighbor().flatMap(LeafBeachNode::buildEvent).ifPresent(q); 113 | getRightNeighbor().flatMap(LeafBeachNode::getRightNeighbor).flatMap(LeafBeachNode::buildEvent).ifPresent(q); 114 | } 115 | 116 | public void subscribe(VertexEvent e) { 117 | subscribedEvents.add(e); 118 | } 119 | 120 | public List getSubscribers() { 121 | return Collections.unmodifiableList(subscribedEvents); 122 | } 123 | } -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/event/Event.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.event; 2 | 3 | import de.alsclo.voronoi.beachline.Beachline; 4 | import de.alsclo.voronoi.graph.Graph; 5 | import de.alsclo.voronoi.graph.Point; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | import java.util.Collection; 10 | 11 | @RequiredArgsConstructor 12 | public abstract class Event implements Comparable { 13 | 14 | @Getter 15 | private final Point point; 16 | 17 | @Override 18 | public int compareTo(Event o) { 19 | return Double.compare(o.point.y, point.y); 20 | } 21 | 22 | @Override 23 | public boolean equals(Object o) { 24 | if (this == o) return true; 25 | if (o == null || getClass() != o.getClass()) return false; 26 | Event event = (Event) o; 27 | return point.equals(event.point); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return point.hashCode(); 33 | } 34 | 35 | public abstract void handle(Collection eventQueue, Beachline beachline, Graph graph); 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/event/SiteEvent.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.event; 2 | 3 | import de.alsclo.voronoi.beachline.Beachline; 4 | import de.alsclo.voronoi.graph.Edge; 5 | import de.alsclo.voronoi.graph.Graph; 6 | import de.alsclo.voronoi.graph.Point; 7 | import lombok.val; 8 | 9 | import java.util.Collection; 10 | 11 | public class SiteEvent extends Event { 12 | 13 | public SiteEvent(Point point) { 14 | super(point); 15 | } 16 | 17 | @Override 18 | public void handle(Collection eventQueue, Beachline beachline, Graph graph) { 19 | val result = beachline.insertArc(getPoint()); 20 | result.splitLeaf.ifPresent(l -> graph.addEdge(new Edge(l.getSite(), getPoint()))); 21 | result.splitLeaf.ifPresent(l -> l.getSubscribers().forEach(eventQueue::remove)); 22 | result.newLeaf.addCircleEvents(eventQueue::add, getPoint().y); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/event/VertexEvent.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.event; 2 | 3 | import de.alsclo.voronoi.beachline.Beachline; 4 | import de.alsclo.voronoi.beachline.LeafBeachNode; 5 | import de.alsclo.voronoi.graph.Edge; 6 | import de.alsclo.voronoi.graph.Graph; 7 | import de.alsclo.voronoi.graph.Point; 8 | import de.alsclo.voronoi.graph.Vertex; 9 | import lombok.ToString; 10 | 11 | import java.util.Collection; 12 | import java.util.Optional; 13 | 14 | import static de.alsclo.voronoi.Math.EPSILON; 15 | import static de.alsclo.voronoi.Math.sq; 16 | import static java.lang.Math.sqrt; 17 | 18 | public class VertexEvent extends Event { 19 | 20 | private final LeafBeachNode l; 21 | private final LeafBeachNode c; 22 | private final LeafBeachNode r; 23 | private final Circle circle; 24 | 25 | private VertexEvent(LeafBeachNode l, LeafBeachNode c, LeafBeachNode r, Circle circle) { 26 | super(new Point(circle.center.x, circle.center.y - circle.radius)); 27 | this.l = l; 28 | this.c = c; 29 | this.r = r; 30 | this.circle = circle; 31 | l.subscribe(this); 32 | c.subscribe(this); 33 | r.subscribe(this); 34 | } 35 | 36 | public static Optional build(LeafBeachNode l, LeafBeachNode c, LeafBeachNode r) { 37 | Point ap = l.getSite(), bp = c.getSite(), cp = r.getSite(); 38 | double convergence = (ap.y - bp.y) * (bp.x - cp.x) - (bp.y - cp.y) * (ap.x - bp.x); 39 | if (convergence > 0) { 40 | Circle circle = new Circle(ap, bp, cp); 41 | if (circle.isValid()) { 42 | return Optional.of(new VertexEvent(l, c, r, circle)); 43 | } 44 | } 45 | return Optional.empty(); 46 | } 47 | 48 | @Override 49 | public void handle(Collection eventQueue, Beachline beachline, Graph graph) { 50 | assert c.getLeftNeighbor().filter(l::equals).isPresent(); 51 | assert c.getRightNeighbor().filter(r::equals).isPresent(); 52 | assert l.getRightNeighbor().filter(c::equals).isPresent(); 53 | assert r.getLeftNeighbor().filter(c::equals).isPresent(); 54 | 55 | if (graph.getSitePoints().stream().noneMatch(circle::contains)) { 56 | c.remove(); 57 | c.getSubscribers().forEach(eventQueue::remove); 58 | 59 | Vertex v = new Vertex(circle.center); 60 | graph.getEdgeBetweenSites(l.getSite(), c.getSite()).get().addVertex(v); 61 | Optional rcOE = graph.getEdgeBetweenSites(r.getSite(), c.getSite()); 62 | if (rcOE.isPresent()) { 63 | rcOE.get().addVertex(v); 64 | } 65 | Edge e = new Edge(l.getSite(), r.getSite()); 66 | graph.addEdge(e); 67 | e.addVertex(v); 68 | 69 | l.addCircleEvents(eventQueue::add, getPoint().y); 70 | r.addCircleEvents(eventQueue::add, getPoint().y); 71 | } 72 | } 73 | 74 | @ToString 75 | private static class Circle { 76 | private final Point center; 77 | private final double radius; 78 | 79 | private Circle(Point l, Point c, Point r) { 80 | if (l.x != c.x && c.x != r.x) { 81 | center = computeCenter(l, c, r); 82 | } else if (c.x != l.x && r.x != l.x) { 83 | center = computeCenter(c, l, r); 84 | } else if (c.x != r.x && l.x != r.x) { 85 | center = computeCenter(c, r, l); 86 | } else { 87 | center = new Point(Double.NaN, Double.NaN); 88 | } 89 | radius = sqrt(sq(c.x - center.x) + sq(c.y - center.y)); 90 | } 91 | 92 | private static Point computeCenter(Point l, Point c, Point r) { 93 | double ma = (c.y - l.y) / (c.x - l.x); 94 | double mb = (r.y - c.y) / (r.x - c.x); 95 | 96 | double x = (ma * mb * (l.y - r.y) + mb * (l.x + c.x) - ma * (c.x + r.x)) / (2.0 * (mb - ma)); 97 | if (ma != 0.0) { 98 | double y = -(x - (l.x + c.x) / 2.0) / ma + (l.y + c.y) / 2.0; 99 | return new Point(x, y); 100 | } else { 101 | double y = -(x - (c.x + r.x) / 2.0) / mb + (c.y + r.y) / 2.0; 102 | return new Point(x, y); 103 | } 104 | } 105 | 106 | public boolean isValid() { 107 | return Double.isFinite(radius); 108 | } 109 | 110 | public boolean contains(Point p) { 111 | return sqrt(sq(p.x - center.x) + sq(p.y - center.y)) < (radius - EPSILON); 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/graph/BisectorMap.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.graph; 2 | 3 | import lombok.NonNull; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.ToString; 6 | 7 | import java.util.Collection; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.stream.Stream; 11 | 12 | class BisectorMap { 13 | 14 | private final Map data = new HashMap<>(); 15 | 16 | void put(Point a, Point b, Edge e) { 17 | assert !data.containsKey(new Bisector(a, b)); 18 | data.put(new Bisector(a, b), e); 19 | } 20 | 21 | Edge get(Point a, Point b) { 22 | return data.get(new Bisector(a, b)); 23 | } 24 | 25 | Collection values() { 26 | return data.values(); 27 | } 28 | 29 | @Override 30 | public boolean equals(Object o) { 31 | if (this == o) return true; 32 | if (o == null || getClass() != o.getClass()) return false; 33 | 34 | BisectorMap that = (BisectorMap) o; 35 | 36 | return data.size() == that.data.size() && data.keySet().stream().allMatch(that.data.keySet()::contains); 37 | } 38 | 39 | @Override 40 | public int hashCode() { 41 | return data.hashCode(); 42 | } 43 | 44 | Stream stream() { 45 | return data.values().stream(); 46 | } 47 | 48 | @RequiredArgsConstructor 49 | @ToString 50 | private static class Bisector { 51 | @NonNull 52 | private final Point a, b; 53 | 54 | @Override 55 | public boolean equals(Object o) { 56 | if (this == o) return true; 57 | if (o == null || getClass() != o.getClass()) return false; 58 | Bisector bisector = (Bisector) o; 59 | return (a.equals(bisector.a) && b.equals(bisector.b)) || (a.equals(bisector.b) && b.equals(bisector.a)); 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return a.hashCode() + b.hashCode(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/graph/Edge.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.graph; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | public class Edge { 8 | @Getter 9 | private final Point site1, site2; 10 | @Getter 11 | private Vertex a, b; 12 | 13 | public void addVertex(Vertex v) { 14 | if (a == null) { 15 | a = v; 16 | } else if (b == null) { 17 | b = v; 18 | } else { 19 | throw new IllegalStateException("Trying to set a third vertex on an edge"); 20 | } 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "Edge("+a+", "+b+")"; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) return true; 31 | if (o == null || getClass() != o.getClass()) return false; 32 | 33 | Edge edge = (Edge) o; 34 | 35 | if (!site1.equals(edge.site1)) return false; 36 | if (!site2.equals(edge.site2)) return false; 37 | if (a != null ? !a.equals(edge.a) : edge.a != null) return false; 38 | return b != null ? b.equals(edge.b) : edge.b == null; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | int result = site1.hashCode(); 44 | result = 31 * result + site2.hashCode(); 45 | result = 31 * result + (a != null ? a.hashCode() : 0); 46 | result = 31 * result + (b != null ? b.hashCode() : 0); 47 | return result; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/graph/Graph.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.graph; 2 | 3 | import lombok.EqualsAndHashCode; 4 | 5 | import java.util.Collections; 6 | import java.util.HashSet; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | import java.util.stream.Stream; 10 | 11 | @EqualsAndHashCode 12 | public class Graph { 13 | 14 | private final BisectorMap edges = new BisectorMap(); 15 | private final Set sites = new HashSet<>(); 16 | 17 | public void addEdge(Edge e) { 18 | edges.put(e.getSite1(), e.getSite2(), e); 19 | } 20 | 21 | /** 22 | * Gets the edge that bisects the space between the two sites if there is one. 23 | * 24 | * @param a point of the first site 25 | * @param b point of the second site 26 | * @return an Optional containing the bisecting edge or an empty Optional if none exist 27 | */ 28 | public Optional getEdgeBetweenSites(Point a, Point b) { 29 | return Optional.ofNullable(edges.get(a, b)); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | StringBuilder sb = new StringBuilder(); 35 | sb.append("Graph["); 36 | edges.values().stream().map(e -> String.format("(%s,%s),\n", e.getA(), e.getB())).sorted().forEachOrdered(sb::append); 37 | sb.append("]"); 38 | return sb.toString(); 39 | } 40 | 41 | public void addSite(Point newSite) { 42 | sites.add(newSite); 43 | } 44 | 45 | public Set getSitePoints() { 46 | return Collections.unmodifiableSet(sites); 47 | } 48 | 49 | public Stream edgeStream() { 50 | return edges.stream(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/graph/Point.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.graph; 2 | 3 | import static de.alsclo.voronoi.Math.EPSILON; 4 | import static de.alsclo.voronoi.Math.PRECISION; 5 | import static java.lang.Math.abs; 6 | 7 | public class Point { 8 | 9 | public final double x; 10 | public final double y; 11 | 12 | public Point(double x, double y) { 13 | this.x = x; 14 | this.y = y; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | 22 | Point point = (Point) o; 23 | return abs(x - point.x) <= EPSILON && abs(y - point.y) <= EPSILON; 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return (int) (x * PRECISION * 31) + (int) (y * PRECISION); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return String.format("(%.2f,%.2f)", x, y); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/alsclo/voronoi/graph/Vertex.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.graph; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | public class Vertex { 8 | @Getter 9 | private final Point location; 10 | 11 | @Override 12 | public String toString() { 13 | return location.toString(); 14 | } 15 | 16 | @Override 17 | public boolean equals(Object o) { 18 | if (this == o) return true; 19 | if (o == null || getClass() != o.getClass()) return false; 20 | 21 | Vertex vertex = (Vertex) o; 22 | 23 | return location.equals(vertex.location); 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return location.hashCode(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/de/alsclo/voronoi/VoronoiTest.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi; 2 | 3 | import de.alsclo.voronoi.graph.Edge; 4 | import de.alsclo.voronoi.graph.Graph; 5 | import de.alsclo.voronoi.graph.Point; 6 | import de.alsclo.voronoi.graph.Vertex; 7 | import lombok.val; 8 | import org.junit.Test; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertNotNull; 16 | import static org.junit.Assert.fail; 17 | 18 | 19 | public class VoronoiTest { 20 | 21 | @Test 22 | public void test() { 23 | val p1 = new Point(0.5, 1.5); 24 | val p2 = new Point(1.5, 1.5); 25 | val p3 = new Point(1.5, 0.5); 26 | val p4 = new Point(0.5, 0.5); 27 | 28 | val c = (new Point(1, 1)); 29 | 30 | val points = Arrays.asList(c, p1, p2, p3, p4); 31 | 32 | Voronoi diagram = new Voronoi(points); 33 | 34 | Graph referenceGraph = new Graph(); 35 | points.forEach(referenceGraph::addSite); 36 | referenceGraph.addEdge(new Edge(p1, p2)); 37 | referenceGraph.addEdge(new Edge(p2, p3)); 38 | referenceGraph.addEdge(new Edge(p3, p4)); 39 | referenceGraph.addEdge(new Edge(p4, p1)); 40 | referenceGraph.addEdge(new Edge(p1, c)); 41 | referenceGraph.addEdge(new Edge(p2, c)); 42 | referenceGraph.addEdge(new Edge(p3, c)); 43 | referenceGraph.addEdge(new Edge(p4, c)); 44 | Vertex v1 = new Vertex(new Point(1.0, 1.5)); 45 | referenceGraph.getEdgeBetweenSites(p1, p2).get().addVertex(v1); 46 | referenceGraph.getEdgeBetweenSites(p1, c).get().addVertex(v1); 47 | referenceGraph.getEdgeBetweenSites(p2, c).get().addVertex(v1); 48 | Vertex v2 = new Vertex(new Point(1.5, 1.0)); 49 | referenceGraph.getEdgeBetweenSites(p2, p3).get().addVertex(v2); 50 | referenceGraph.getEdgeBetweenSites(p2, c).get().addVertex(v2); 51 | referenceGraph.getEdgeBetweenSites(p3, c).get().addVertex(v2); 52 | Vertex v3 = new Vertex(new Point(1.0, 0.5)); 53 | referenceGraph.getEdgeBetweenSites(p3, p4).get().addVertex(v3); 54 | referenceGraph.getEdgeBetweenSites(p3, c).get().addVertex(v3); 55 | referenceGraph.getEdgeBetweenSites(p4, c).get().addVertex(v3); 56 | Vertex v4 = new Vertex(new Point(0.5, 1.0)); 57 | referenceGraph.getEdgeBetweenSites(p4, p1).get().addVertex(v4); 58 | referenceGraph.getEdgeBetweenSites(p4, c).get().addVertex(v4); 59 | referenceGraph.getEdgeBetweenSites(p1, c).get().addVertex(v4); 60 | 61 | assertEquals(referenceGraph, diagram.getGraph()); 62 | } 63 | 64 | @Test 65 | public void testBoundingBox1() { 66 | try { 67 | new Voronoi(Arrays.asList(new Point(10, 0), new Point(0, 10))) 68 | .applyBoundingBox(0, 0, 5, 5); 69 | fail(); 70 | } catch (IllegalArgumentException ignored) {} 71 | } 72 | 73 | @Test 74 | public void testBoundingBox2() { 75 | 76 | } 77 | 78 | @Test 79 | public void testColinear() { 80 | List points = new ArrayList(); 81 | for (int x = 0; x <= 4; x +=2) { 82 | for (int y = 0; y <= 4; y+= 2) { 83 | points.add(new Point(x, y)); 84 | } 85 | } 86 | 87 | Voronoi diagram = new Voronoi(points); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/test/java/de/alsclo/voronoi/beachline/BeachlineTests.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.beachline; 2 | 3 | import de.alsclo.voronoi.graph.Point; 4 | import lombok.val; 5 | import org.junit.Test; 6 | 7 | import static org.junit.Assert.*; 8 | 9 | public class BeachlineTests { 10 | 11 | @Test 12 | public void testInsert() { 13 | Beachline bl = new Beachline(); 14 | assertNull(bl.getRoot()); 15 | LeafBeachNode root = bl.insertArc(new Point(0,0)).newLeaf; 16 | assertEquals(root, bl.getRoot()); 17 | LeafBeachNode first = bl.insertArc(new Point(0.5, 4)).newLeaf; 18 | assertNotEquals(root, bl.getRoot()); 19 | assertTrue(bl.getRoot() instanceof InnerBeachNode); 20 | InnerBeachNode newRoot = (InnerBeachNode) bl.getRoot(); 21 | assertEquals(newRoot.getLeftmostLeaf(), newRoot.getRightmostLeaf()); 22 | assertEquals(newRoot.getLeftChild(), root); 23 | assertTrue(newRoot.getRightChild() instanceof InnerBeachNode); 24 | InnerBeachNode tmp = (InnerBeachNode) newRoot.getRightChild(); 25 | assertEquals(tmp.getRightChild(), root); 26 | assertEquals(tmp.getLeftChild(), first); 27 | } 28 | 29 | @Test 30 | public void testNeightbor() { 31 | Beachline bl = new Beachline(); 32 | // O 33 | // / \ 34 | // O O 35 | // /| |\ 36 | // 1 2 3 4 37 | val l1 = new LeafBeachNode(new Point(1, 0)); 38 | val l2 = new LeafBeachNode(new Point(2, 0)); 39 | val l3 = new LeafBeachNode(new Point(3, 0)); 40 | val l4 = new LeafBeachNode(new Point(4, 0)); 41 | bl.setRoot(new InnerBeachNode(new InnerBeachNode(l1, l2), new InnerBeachNode(l3, l4))); 42 | 43 | assertFalse(l1.getLeftNeighbor().isPresent()); 44 | assertFalse(l4.getRightNeighbor().isPresent()); 45 | assertTrue(l1.getRightNeighbor().orElse(null) == l2); 46 | assertTrue(l2.getRightNeighbor().orElse(null) == l3); 47 | assertTrue(l3.getRightNeighbor().orElse(null) == l4); 48 | assertTrue(l4.getLeftNeighbor().orElse(null) == l3); 49 | assertTrue(l3.getLeftNeighbor().orElse(null) == l2); 50 | assertTrue(l2.getLeftNeighbor().orElse(null) == l1); 51 | 52 | 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/de/alsclo/voronoi/gui/RenderVoronoi.java: -------------------------------------------------------------------------------- 1 | package de.alsclo.voronoi.gui; 2 | 3 | import de.alsclo.voronoi.Voronoi; 4 | import de.alsclo.voronoi.graph.Point; 5 | 6 | import javax.swing.*; 7 | import java.awt.*; 8 | import java.util.Random; 9 | import java.util.stream.Collectors; 10 | import java.util.stream.Stream; 11 | 12 | public class RenderVoronoi extends JFrame{ 13 | 14 | private static final int size = 512; 15 | 16 | private static final double POINT_SIZE = 5.0; 17 | private final Voronoi diagram; 18 | 19 | public RenderVoronoi(Voronoi diagram) { 20 | this.diagram = diagram; 21 | } 22 | 23 | public void paint(Graphics g) { 24 | Graphics2D g2 = (Graphics2D) g; 25 | for (Point site : diagram.getGraph().getSitePoints()) { 26 | g2.fillOval((int) Math.round(site.x-POINT_SIZE/2), size - (int) Math.round(site.y+POINT_SIZE/2) + 32, (int)POINT_SIZE, (int)POINT_SIZE); 27 | // g2.drawString(String.format("%d,%d", (int)site.x, (int)site.y), (int) site.x, size - (int)site.y + 32); 28 | } 29 | 30 | diagram.getGraph().edgeStream().filter(e -> e.getA() != null && e.getB() != null).forEach(e -> { 31 | Point a = e.getA().getLocation(); 32 | Point b = e.getB().getLocation(); 33 | g2.drawLine((int)a.x, size - (int)a.y + 32, (int)b.x, size - (int)b.y + 32); 34 | }); 35 | } 36 | 37 | public static void main(String[] args) { 38 | Random r = new Random(9235563856L); 39 | Stream gen = Stream.generate(() -> new Point(r.nextDouble() * size, r.nextDouble() * size)); 40 | Voronoi diagram = new Voronoi(gen.limit(1024).collect(Collectors.toList())).relax().relax(); 41 | // assert diagram.getGraph().edgeStream().noneMatch(e -> e.getA() == null && e.getB() == null); 42 | 43 | diagram.getGraph().edgeStream().forEach(System.out::println); 44 | RenderVoronoi frame = new RenderVoronoi(diagram); 45 | frame.setDefaultCloseOperation(DISPOSE_ON_CLOSE); 46 | frame.setSize(size, size+32); 47 | frame.setVisible(true); 48 | } 49 | } 50 | --------------------------------------------------------------------------------