├── .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] [](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 |
--------------------------------------------------------------------------------