├── .gitattributes
├── .gitignore
├── README.txt
├── pom.xml
└── src
└── main
└── java
└── com
└── hoten
└── delaunay
├── examples
├── TestDriver.java
└── TestGraphImpl.java
├── geom
├── GenUtils.java
├── Point.java
└── Rectangle.java
└── voronoi
├── Center.java
├── Corner.java
├── Edge.java
├── VoronoiGraph.java
├── groundshapes
├── Blob.java
├── HeightAlgorithm.java
├── Perlin.java
└── Radial.java
└── nodename
└── as3delaunay
├── Circle.java
├── Edge.java
├── EdgeList.java
├── EdgeReorderer.java
├── Halfedge.java
├── HalfedgePriorityQueue.java
├── ICoord.java
├── IDisposable.java
├── LR.java
├── LineSegment.java
├── Polygon.java
├── Site.java
├── SiteList.java
├── Triangle.java
├── Vertex.java
├── Voronoi.java
└── Winding.java
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 | #################
4 |
5 | *.pydevproject
6 | .project
7 | .metadata
8 | bin/
9 | tmp/
10 | *.tmp
11 | *.bak
12 | *.swp
13 | *~.nib
14 | local.properties
15 | .classpath
16 | .settings/
17 | .loadpath
18 |
19 | # External tool builders
20 | .externalToolBuilders/
21 |
22 | # Locally stored "Eclipse launch configurations"
23 | *.launch
24 |
25 | # CDT-specific
26 | .cproject
27 |
28 | # PDT-specific
29 | .buildpath
30 |
31 |
32 | #################
33 | ## IntelliJ IDEA
34 | #################
35 |
36 | .idea
37 | *.iml
38 |
39 | #################
40 | ## Visual Studio
41 | #################
42 |
43 | ## Ignore Visual Studio temporary files, build results, and
44 | ## files generated by popular Visual Studio add-ons.
45 |
46 | # User-specific files
47 | *.suo
48 | *.user
49 | *.sln.docstates
50 |
51 | # Build results
52 |
53 | [Dd]ebug/
54 | [Rr]elease/
55 | x64/
56 | build/
57 | [Bb]in/
58 | [Oo]bj/
59 |
60 | # MSTest test Results
61 | [Tt]est[Rr]esult*/
62 | [Bb]uild[Ll]og.*
63 |
64 | *_i.c
65 | *_p.c
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.pch
70 | *.pdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.log
86 | *.scc
87 |
88 | # Visual C++ cache files
89 | ipch/
90 | *.aps
91 | *.ncb
92 | *.opensdf
93 | *.sdf
94 | *.cachefile
95 |
96 | # Visual Studio profiler
97 | *.psess
98 | *.vsp
99 | *.vspx
100 |
101 | # Guidance Automation Toolkit
102 | *.gpState
103 |
104 | # ReSharper is a .NET coding add-in
105 | _ReSharper*/
106 | *.[Rr]e[Ss]harper
107 |
108 | # TeamCity is a build add-in
109 | _TeamCity*
110 |
111 | # DotCover is a Code Coverage Tool
112 | *.dotCover
113 |
114 | # NCrunch
115 | *.ncrunch*
116 | .*crunch*.local.xml
117 |
118 | # Installshield output folder
119 | [Ee]xpress/
120 |
121 | # DocProject is a documentation generator add-in
122 | DocProject/buildhelp/
123 | DocProject/Help/*.HxT
124 | DocProject/Help/*.HxC
125 | DocProject/Help/*.hhc
126 | DocProject/Help/*.hhk
127 | DocProject/Help/*.hhp
128 | DocProject/Help/Html2
129 | DocProject/Help/html
130 |
131 | # Click-Once directory
132 | publish/
133 |
134 | # Publish Web Output
135 | *.Publish.xml
136 | *.pubxml
137 |
138 | # NuGet Packages Directory
139 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
140 | #packages/
141 |
142 | # Windows Azure Build Output
143 | csx
144 | *.build.csdef
145 |
146 | # Windows Store app package directory
147 | AppPackages/
148 |
149 | # Others
150 | sql/
151 | *.Cache
152 | ClientBin/
153 | [Ss]tyle[Cc]op.*
154 | ~$*
155 | *~
156 | *.dbmdl
157 | *.[Pp]ublish.xml
158 | *.pfx
159 | *.publishsettings
160 |
161 | # RIA/Silverlight projects
162 | Generated_Code/
163 |
164 | # Backup & report files from converting an old project file to a newer
165 | # Visual Studio version. Backup files are not needed, because we have git ;-)
166 | _UpgradeReport_Files/
167 | Backup*/
168 | UpgradeLog*.XML
169 | UpgradeLog*.htm
170 |
171 | # SQL Server files
172 | App_Data/*.mdf
173 | App_Data/*.ldf
174 |
175 | #############
176 | ## Windows detritus
177 | #############
178 |
179 | # Windows image file caches
180 | Thumbs.db
181 | ehthumbs.db
182 |
183 | # Folder config file
184 | Desktop.ini
185 |
186 | # Recycle Bin used on file shares
187 | $RECYCLE.BIN/
188 |
189 | # Mac crap
190 | .DS_Store
191 |
192 |
193 | #############
194 | ## Python
195 | #############
196 |
197 | *.py[co]
198 |
199 | # Packages
200 | *.egg
201 | *.egg-info
202 | dist/
203 | build/
204 | eggs/
205 | parts/
206 | var/
207 | sdist/
208 | develop-eggs/
209 | .installed.cfg
210 |
211 | # Installer logs
212 | pip-log.txt
213 |
214 | # Unit test / coverage reports
215 | .coverage
216 | .tox
217 |
218 | #Translations
219 | *.mo
220 |
221 | #Mr Developer
222 | .mr.developer.cfg
223 | /target/
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | /*
2 | * Java implementation by Connor Clark (www.hotengames.com). Pretty much a 1:1
3 | * translation of a wonderful map generating algorthim by Amit Patel of Red Blob Games,
4 | * which can be found here (http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/)
5 | * Hopefully it's of use to someone out there who needed it in Java like I did!
6 | *
7 | * FORTUNE'S ALGORTIHIM
8 | *
9 | * This is a java implementation of an AS3 (Flash) implementation of an algorthim
10 | * originally created in C++. Pretty much a 1:1 translation from as3 to java, save
11 | * for some necessary workarounds. Original as3 implementation by Alan Shaw (of nodename)
12 | * can be found here (https://github.com/nodename/as3delaunay). Original algorthim
13 | * by Steven Fortune (see lisence for c++ implementation below)
14 | *
15 | * The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
16 | * Bell Laboratories.
17 | * Permission to use, copy, modify, and distribute this software for any
18 | * purpose without fee is hereby granted, provided that this entire notice
19 | * is included in all copies of any software which is or includes a copy
20 | * or modification of this software and in all copies of the supporting
21 | * documentation for such software.
22 | * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
23 | * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
24 | * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
25 | * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
26 | */
27 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 | com.hoten.delaunay
5 | java-delaunay
6 | 0.1
7 | jar
8 |
9 |
10 |
11 | org.apache.maven.plugins
12 | maven-compiler-plugin
13 | 2.3.2
14 |
15 | false
16 |
17 |
18 |
19 |
20 |
21 |
22 | junit
23 | junit
24 | 4.10
25 | test
26 |
27 |
28 |
29 | UTF-8
30 | 1.8
31 | 1.8
32 |
33 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/examples/TestDriver.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.examples;
2 |
3 | import com.hoten.delaunay.voronoi.VoronoiGraph;
4 | import com.hoten.delaunay.voronoi.groundshapes.*;
5 | import com.hoten.delaunay.voronoi.nodename.as3delaunay.Voronoi;
6 |
7 | import javax.imageio.ImageIO;
8 | import javax.swing.*;
9 | import java.awt.*;
10 | import java.awt.event.MouseAdapter;
11 | import java.awt.event.MouseEvent;
12 | import java.awt.event.MouseWheelEvent;
13 | import java.awt.image.BufferedImage;
14 | import java.io.File;
15 | import java.io.IOException;
16 | import java.util.HashMap;
17 | import java.util.Random;
18 | import java.util.regex.Matcher;
19 | import java.util.regex.Pattern;
20 |
21 | /**
22 | *
How to use:
23 | * Just change constants to customize graph, it's shape and image.
24 | */
25 | public class TestDriver {
26 |
27 | /** Do you really need to save image? */
28 | private static final boolean SAVE_FILE = false;
29 |
30 | /** The side of the square in which the graph will be fitted. */
31 | private static final int GRAPH_BOUNDS = 2048;
32 |
33 | /** Size of image which will be drawn. */
34 | private static final int FRAME_BOUNDS = 512;
35 |
36 | /** Number of pieces for the graph. */
37 | private static final int SITES_AMOUNT = 8_000;
38 |
39 | /**
40 | * Each time a relaxation step is performed, the points are left in a slightly more even distribution:
41 | * closely spaced points move farther apart, and widely spaced points move closer together.
42 | */
43 | private static final int LLOYD_RELAXATIONS = 2;
44 |
45 | /** Randomizing number. Use it with {@link #RANDOM_SEED} = false to get same image every time. */
46 | private static long SEED = 123L;
47 |
48 | /** You can make it false if you want to check some changes in code or image/graph size. */
49 | private static final boolean RANDOM_SEED = true;
50 |
51 | /** Random, radial, blob, etc. See {@link #getAlgorithmImplementation(Random, String)} */
52 | private static final String ALGORITHM = "perlin";
53 |
54 | public static void main(String[] args) throws IOException {
55 | if (RANDOM_SEED) SEED = System.nanoTime();
56 |
57 | printInfo();
58 |
59 | final BufferedImage img = createVoronoiGraph(GRAPH_BOUNDS, SITES_AMOUNT, LLOYD_RELAXATIONS, SEED, ALGORITHM).createMap();
60 |
61 | saveFile(img);
62 |
63 | showGraph(img);
64 | }
65 |
66 | private static void printInfo() {
67 | System.out.println("Seed: " + SEED);
68 | System.out.println("Bounds: " + GRAPH_BOUNDS);
69 | System.out.println("Sites: " + SITES_AMOUNT);
70 | System.out.println("Shape: " + ALGORITHM);
71 | System.out.println("Relaxs: " + LLOYD_RELAXATIONS);
72 | System.out.println("=============================");
73 | }
74 |
75 | public static VoronoiGraph createVoronoiGraph(int bounds, int numSites, int numLloydRelaxations, long seed, String algorithmName) {
76 | final Random r = new Random(seed);
77 | HeightAlgorithm algorithm = getAlgorithmImplementation(r, algorithmName);
78 |
79 | //make the intial underlying voronoi structure
80 | final Voronoi v = new Voronoi(numSites, bounds, bounds, r, null);
81 |
82 | //assemble the voronoi strucutre into a usable graph object representing a map
83 | final TestGraphImpl graph = new TestGraphImpl(v, numLloydRelaxations, r, algorithm);
84 |
85 | return graph;
86 | }
87 |
88 | /**
89 | * Currently there are only 1 algorithm. You can choose one of algorithms exactly or random from this list:
90 | *
91 | * - random
92 | * - radial
93 | * - blob
94 | * - perlin
95 | *
96 | *
97 | * @param r Randomizer.
98 | * @param name Name of the algorithm.
99 | * @return
100 | */
101 | private static HeightAlgorithm getAlgorithmImplementation(Random r, String name) {
102 | HashMap implementations = new HashMap<>();
103 | implementations.put("random", 0);
104 | implementations.put("radial", 1);
105 | implementations.put("blob", 2);
106 | implementations.put("perlin", 3);
107 | int i = implementations.getOrDefault(name, 0);
108 | if (i == 0) i = 1 + r.nextInt(implementations.size() - 1);
109 | switch (i) {
110 | case 1: return new Radial(1.07,
111 | r.nextInt(5) + 1,
112 | r.nextDouble() * 2 * Math.PI,
113 | r.nextDouble() * 2 * Math.PI,
114 | r.nextDouble() * .5 + .2);
115 | case 2: return new Blob();
116 | case 3: return new Perlin(r, 7, 256, 256);
117 | default: throw new RuntimeException("Method \"getAlgorithmImplementation()\" is broken. " +
118 | "Check implementations map and switch statement. Their values and cases must match.");
119 | }
120 | }
121 |
122 | private static void saveFile(BufferedImage img) throws IOException {
123 | if (SAVE_FILE) {
124 | File file = new File("output/");
125 | file.mkdirs();
126 | file = new File(String.format("output/seed-%s-sites-%d-lloyds-%d.png", SEED, SITES_AMOUNT, LLOYD_RELAXATIONS));
127 | while (file.exists()) file = new File(incrementFileName(file.getPath()));
128 | ImageIO.write(img, "PNG", file);
129 | }
130 | }
131 |
132 | /**
133 | * If you have equal filenames - use this method to change filename before creating it.
134 | *
135 | * @param oldName fileName_index1.format(fileName.format)
136 | * @return fileName_index2.format(fileName_1.format)
137 | */
138 | private static String incrementFileName(String oldName) {
139 | String newName;
140 | int i = oldName.lastIndexOf('.');
141 | Matcher m = Pattern.compile("\\((\\d+)\\).").matcher(oldName);
142 | if (m.find()) {
143 | String n = String.valueOf(Integer.valueOf(m.group(1)) + 1);
144 | newName = oldName.substring(0, m.start()) + "(" + n + ")" + oldName.substring(i);
145 | } else {
146 | newName = oldName.substring(0, i) + "(1)" + oldName.substring(i);
147 | }
148 | return newName;
149 | }
150 |
151 | private static int oldX = -1, oldY = -1;
152 | private static int drawX = 0, drawY = 0;
153 | private static int zoom = 1;
154 | private static float zoomModifier = 1;
155 |
156 | private static void showGraph(final BufferedImage img) {
157 | final JFrame frame = new JFrame() {
158 | @Override
159 | public void paint(Graphics g) {
160 | g.fillRect(0, 0, getWidth(), getHeight());
161 | g.drawImage(img,
162 | getInsets().left + drawX,
163 | getInsets().top - drawY,
164 | (int)(GRAPH_BOUNDS * zoomModifier),
165 | (int)(GRAPH_BOUNDS * zoomModifier),
166 | null);
167 | }
168 | };
169 |
170 | frame.setTitle("Java Fortune");
171 | frame.setVisible(true);
172 | frame.setSize(FRAME_BOUNDS, FRAME_BOUNDS);
173 | frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
174 |
175 | frame.addMouseListener(new MouseAdapter() {
176 | @Override
177 | public void mousePressed(MouseEvent e) {
178 | oldX = e.getX();
179 | oldY = e.getY();
180 | }
181 | @Override
182 | public void mouseReleased(MouseEvent e) {
183 | oldX = -1;
184 | oldY = -1;
185 | }
186 | });
187 | frame.addMouseMotionListener(new MouseAdapter() {
188 | @Override
189 | public void mouseDragged(MouseEvent e) {
190 | if (oldX != -1) {
191 | int dx = e.getX() - oldX, dy = e.getY() - oldY;
192 | drawX = Math.min(0, Math.max(FRAME_BOUNDS - (int)(GRAPH_BOUNDS * zoomModifier), drawX + dx));
193 | drawY = Math.min((int)(GRAPH_BOUNDS * zoomModifier) - FRAME_BOUNDS, Math.max(0, drawY - dy));
194 | oldX += dx;
195 | oldY += dy;
196 | frame.repaint();
197 | }
198 | }
199 | });
200 | frame.addMouseWheelListener(new MouseAdapter() {
201 | @Override
202 | public void mouseWheelMoved(MouseWheelEvent e) {
203 | zoom = Math.min(10, Math.max(-5, zoom + e.getWheelRotation()));
204 | zoomModifier = zoom > 0 ? zoom : 1/(float)(1 - zoom);
205 | frame.repaint();
206 | }
207 | });
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/examples/TestGraphImpl.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.examples;
2 |
3 | import com.hoten.delaunay.voronoi.Center;
4 | import com.hoten.delaunay.voronoi.VoronoiGraph;
5 | import com.hoten.delaunay.voronoi.groundshapes.HeightAlgorithm;
6 | import com.hoten.delaunay.voronoi.nodename.as3delaunay.Voronoi;
7 | import java.awt.Color;
8 | import java.util.Random;
9 |
10 | /**
11 | * TestGraphImpl.java
12 | *
13 | * Supplies information for Voronoi graphing logic:
14 | *
15 | * 1) biome mapping information based on a Site's elevation and moisture
16 | *
17 | * 2) color mapping information based on biome, and for bodies of water
18 | *
19 | * @author Connor
20 | */
21 | public class TestGraphImpl extends VoronoiGraph {
22 |
23 | public static enum ColorData {
24 |
25 | OCEAN(0x44447a), LAKE(0x336699), BEACH(0xa09077), SNOW(0xffffff),
26 | TUNDRA(0xbbbbaa), BARE(0x888888), SCORCHED(0x555555), TAIGA(0x99aa77),
27 | SHURBLAND(0x889977), TEMPERATE_DESERT(0xc9d29b),
28 | TEMPERATE_RAIN_FOREST(0x448855), TEMPERATE_DECIDUOUS_FOREST(0x679459),
29 | GRASSLAND(0x88aa55), SUBTROPICAL_DESERT(0xd2b98b), SHRUBLAND(0x889977),
30 | ICE(0x99ffff), MARSH(0x2f6666), TROPICAL_RAIN_FOREST(0x337755),
31 | TROPICAL_SEASONAL_FOREST(0x559944), COAST(0x33335a),
32 | LAKESHORE(0x225588), RIVER(0x225588);
33 | public Color color;
34 |
35 | ColorData(int color) {
36 | this.color = new Color(color);
37 | }
38 | }
39 |
40 | public TestGraphImpl(Voronoi v, int numLloydRelaxations, Random r, HeightAlgorithm algorithm) {
41 | super(v, numLloydRelaxations, r, algorithm);
42 | OCEAN = ColorData.OCEAN.color;
43 | LAKE = ColorData.LAKE.color;
44 | BEACH = ColorData.BEACH.color;
45 | RIVER = ColorData.RIVER.color;
46 | }
47 |
48 | @Override
49 | protected Color getColor(Enum biome) {
50 | return ((ColorData) biome).color;
51 | }
52 |
53 | @Override
54 | protected Enum getBiome(Center p) {
55 | if (p.ocean) {
56 | return ColorData.OCEAN;
57 | } else if (p.water) {
58 | if (p.elevation < 0.1) {
59 | return ColorData.MARSH;
60 | }
61 | if (p.elevation > 0.8) {
62 | return ColorData.ICE;
63 | }
64 | return ColorData.LAKE;
65 | } else if (p.coast) {
66 | return ColorData.BEACH;
67 | } else if (p.elevation > 0.8) {
68 | if (p.moisture > 0.50) {
69 | return ColorData.SNOW;
70 | } else if (p.moisture > 0.33) {
71 | return ColorData.TUNDRA;
72 | } else if (p.moisture > 0.16) {
73 | return ColorData.BARE;
74 | } else {
75 | return ColorData.SCORCHED;
76 | }
77 | } else if (p.elevation > 0.6) {
78 | if (p.moisture > 0.66) {
79 | return ColorData.TAIGA;
80 | } else if (p.moisture > 0.33) {
81 | return ColorData.SHRUBLAND;
82 | } else {
83 | return ColorData.TEMPERATE_DESERT;
84 | }
85 | } else if (p.elevation > 0.3) {
86 | if (p.moisture > 0.83) {
87 | return ColorData.TEMPERATE_RAIN_FOREST;
88 | } else if (p.moisture > 0.50) {
89 | return ColorData.TEMPERATE_DECIDUOUS_FOREST;
90 | } else if (p.moisture > 0.16) {
91 | return ColorData.GRASSLAND;
92 | } else {
93 | return ColorData.TEMPERATE_DESERT;
94 | }
95 | } else {
96 | if (p.moisture > 0.66) {
97 | return ColorData.TROPICAL_RAIN_FOREST;
98 | } else if (p.moisture > 0.33) {
99 | return ColorData.TROPICAL_SEASONAL_FOREST;
100 | } else if (p.moisture > 0.16) {
101 | return ColorData.GRASSLAND;
102 | } else {
103 | return ColorData.SUBTROPICAL_DESERT;
104 | }
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/geom/GenUtils.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.geom;
2 |
3 | /**
4 | * GenUtil.java
5 | *
6 | * @author Connor
7 | */
8 | public class GenUtils {
9 |
10 | public static boolean closeEnough(double d1, double d2, double diff) {
11 | return Math.abs(d1 - d2) <= diff;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/geom/Point.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.geom;
2 |
3 | /**
4 | * Point.java
5 | *
6 | * @author Connor
7 | */
8 | public class Point {
9 |
10 | public static double distance(Point _coord, Point _coord0) {
11 | return Math.sqrt((_coord.x - _coord0.x) * (_coord.x - _coord0.x) + (_coord.y - _coord0.y) * (_coord.y - _coord0.y));
12 | }
13 | public double x, y;
14 |
15 | public Point(double x, double y) {
16 | this.x = x;
17 | this.y = y;
18 | }
19 |
20 | @Override
21 | public String toString() {
22 | return x + ", " + y;
23 | }
24 |
25 | public double l2() {
26 | return x * x + y * y;
27 | }
28 |
29 | public double length() {
30 | return Math.sqrt(x * x + y * y);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/geom/Rectangle.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.geom;
2 |
3 | /**
4 | * Rectangle.java
5 | *
6 | * @author Connor
7 | */
8 | public class Rectangle {
9 |
10 | final public double x, y, width, height, right, bottom, left, top;
11 |
12 | public Rectangle(double x, double y, double width, double height) {
13 | left = this.x = x;
14 | top = this.y = y;
15 | this.width = width;
16 | this.height = height;
17 | right = x + width;
18 | bottom = y + height;
19 | }
20 |
21 | public boolean liesOnAxes(Point p) {
22 | return GenUtils.closeEnough(p.x, x, 1) || GenUtils.closeEnough(p.y, y, 1) || GenUtils.closeEnough(p.x, right, 1) || GenUtils.closeEnough(p.y, bottom, 1);
23 | }
24 |
25 | public boolean inBounds(Point p) {
26 | return inBounds(p.x, p.y);
27 | }
28 |
29 | public boolean inBounds(double x0, double y0) {
30 | return !(x0 < x || x0 > right || y0 < y || y0 > bottom);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/Center.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.ArrayList;
5 |
6 | /**
7 | * Center.java
8 | *
9 | * @author Connor
10 | */
11 | public class Center {
12 |
13 | public int index;
14 | public Point loc;
15 | public ArrayList corners = new ArrayList();//good
16 | public ArrayList neighbors = new ArrayList();//good
17 | public ArrayList borders = new ArrayList();
18 | public boolean border, ocean, water, coast;
19 | public double elevation;
20 | public double moisture;
21 | public Enum biome;
22 | public double area;
23 |
24 | public Center() {
25 | }
26 |
27 | public Center(Point loc) {
28 | this.loc = loc;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/Corner.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.ArrayList;
5 |
6 | /**
7 | * Corner.java
8 | *
9 | * @author Connor
10 | */
11 | public class Corner {
12 |
13 | public ArrayList touches = new ArrayList(); //good
14 | public ArrayList adjacent = new ArrayList(); //good
15 | public ArrayList protrudes = new ArrayList();
16 | public Point loc;
17 | public int index;
18 | public boolean border;
19 | public double elevation;
20 | public boolean water, ocean, coast;
21 | public Corner downslope;
22 | public int river;
23 | public double moisture;
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/Edge.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 |
5 | /**
6 | * Edge.java
7 | *
8 | * @author Connor
9 | */
10 | public class Edge {
11 |
12 | public int index;
13 | public Center d0, d1; // Delaunay edge
14 | public Corner v0, v1; // Voronoi edge
15 | public Point midpoint; // halfway between v0,v1
16 | public int river;
17 |
18 | public void setVornoi(Corner v0, Corner v1) {
19 | this.v0 = v0;
20 | this.v1 = v1;
21 | midpoint = new Point((v0.loc.x + v1.loc.x) / 2, (v0.loc.y + v1.loc.y) / 2);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/VoronoiGraph.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 | import com.hoten.delaunay.voronoi.groundshapes.HeightAlgorithm;
6 | import com.hoten.delaunay.voronoi.nodename.as3delaunay.LineSegment;
7 | import com.hoten.delaunay.voronoi.nodename.as3delaunay.Voronoi;
8 |
9 | import java.awt.*;
10 | import java.awt.image.BufferedImage;
11 | import java.util.*;
12 |
13 | /**
14 | * VoronoiGraph.java
15 | *
16 | * @author Connor
17 | */
18 | public abstract class VoronoiGraph {
19 |
20 | final public ArrayList edges = new ArrayList();
21 | final public ArrayList corners = new ArrayList();
22 | final public ArrayList centers = new ArrayList();
23 | final public Rectangle bounds;
24 | final private Random r;
25 | protected Color OCEAN, RIVER, LAKE, BEACH;
26 | final public BufferedImage pixelCenterMap;
27 |
28 | public VoronoiGraph(Voronoi v, int numLloydRelaxations, Random r, HeightAlgorithm algorithm) {
29 | this.r = r;
30 | bounds = v.get_plotBounds();
31 | for (int i = 0; i < numLloydRelaxations; i++) {
32 | ArrayList points = v.siteCoords();
33 | for (Point p : points) {
34 | ArrayList region = v.region(p);
35 | double x = 0;
36 | double y = 0;
37 | for (Point c : region) {
38 | x += c.x;
39 | y += c.y;
40 | }
41 | x /= region.size();
42 | y /= region.size();
43 | p.x = x;
44 | p.y = y;
45 | }
46 | v = new Voronoi(points, null, v.get_plotBounds());
47 | }
48 | buildGraph(v);
49 | improveCorners();
50 |
51 | assignCornerElevations(algorithm);
52 | assignOceanCoastAndLand();
53 | redistributeElevations(landCorners());
54 | assignPolygonElevations();
55 |
56 | calculateDownslopes();
57 | //calculateWatersheds();
58 | createRivers();
59 | assignCornerMoisture();
60 | redistributeMoisture(landCorners());
61 | assignPolygonMoisture();
62 | assignBiomes();
63 |
64 | pixelCenterMap = new BufferedImage((int) bounds.width, (int) bounds.width, BufferedImage.TYPE_4BYTE_ABGR);
65 | }
66 |
67 | abstract protected Enum getBiome(Center p);
68 |
69 | abstract protected Color getColor(Enum biome);
70 |
71 | private void improveCorners() {
72 | Point[] newP = new Point[corners.size()];
73 | for (Corner c : corners) {
74 | if (c.border) {
75 | newP[c.index] = c.loc;
76 | } else {
77 | double x = 0;
78 | double y = 0;
79 | for (Center center : c.touches) {
80 | x += center.loc.x;
81 | y += center.loc.y;
82 | }
83 | newP[c.index] = new Point(x / c.touches.size(), y / c.touches.size());
84 | }
85 | }
86 | corners.stream().forEach((c) -> {
87 | c.loc = newP[c.index];
88 | });
89 | edges.stream().filter((e) -> (e.v0 != null && e.v1 != null)).forEach((e) -> {
90 | e.setVornoi(e.v0, e.v1);
91 | });
92 | }
93 |
94 | private Edge edgeWithCenters(Center c1, Center c2) {
95 | for (Edge e : c1.borders) {
96 | if (e.d0 == c2 || e.d1 == c2) {
97 | return e;
98 | }
99 | }
100 | return null;
101 | }
102 |
103 | private void drawTriangle(Graphics2D g, Corner c1, Corner c2, Center center) {
104 | int[] x = new int[3];
105 | int[] y = new int[3];
106 | x[0] = (int) center.loc.x;
107 | y[0] = (int) center.loc.y;
108 | x[1] = (int) c1.loc.x;
109 | y[1] = (int) c1.loc.y;
110 | x[2] = (int) c2.loc.x;
111 | y[2] = (int) c2.loc.y;
112 | g.fillPolygon(x, y, 3);
113 | }
114 |
115 | private boolean closeEnough(double d1, double d2, double diff) {
116 | return Math.abs(d1 - d2) <= diff;
117 | }
118 |
119 | public BufferedImage createMap() {
120 | int width = (int) bounds.width;
121 | int height = (int) bounds.height;
122 |
123 | final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
124 | Graphics2D g = img.createGraphics();
125 |
126 | paint(g);
127 |
128 | return img;
129 | }
130 |
131 | public void paint(Graphics2D g) {
132 | paint(g, true, true, false, false, false, true);
133 | }
134 |
135 | private void drawPolygon(Graphics2D g, Center c, Color color) {
136 | g.setColor(color);
137 |
138 | //only used if Center c is on the edge of the graph. allows for completely filling in the outer polygons
139 | Corner edgeCorner1 = null;
140 | Corner edgeCorner2 = null;
141 | c.area = 0;
142 | for (Center n : c.neighbors) {
143 | Edge e = edgeWithCenters(c, n);
144 |
145 | if (e.v0 == null) {
146 | //outermost voronoi edges aren't stored in the graph
147 | continue;
148 | }
149 |
150 | //find a corner on the exterior of the graph
151 | //if this Edge e has one, then it must have two,
152 | //finding these two corners will give us the missing
153 | //triangle to render. this special triangle is handled
154 | //outside this for loop
155 | Corner cornerWithOneAdjacent = e.v0.border ? e.v0 : e.v1;
156 | if (cornerWithOneAdjacent.border) {
157 | if (edgeCorner1 == null) {
158 | edgeCorner1 = cornerWithOneAdjacent;
159 | } else {
160 | edgeCorner2 = cornerWithOneAdjacent;
161 | }
162 | }
163 |
164 | drawTriangle(g, e.v0, e.v1, c);
165 | c.area += Math.abs(c.loc.x * (e.v0.loc.y - e.v1.loc.y)
166 | + e.v0.loc.x * (e.v1.loc.y - c.loc.y)
167 | + e.v1.loc.x * (c.loc.y - e.v0.loc.y)) / 2;
168 | }
169 |
170 | //handle the missing triangle
171 | if (edgeCorner2 != null) {
172 | //if these two outer corners are NOT on the same exterior edge of the graph,
173 | //then we actually must render a polygon (w/ 4 points) and take into consideration
174 | //one of the four corners (either 0,0 or 0,height or width,0 or width,height)
175 | //note: the 'missing polygon' may have more than just 4 points. this
176 | //is common when the number of sites are quite low (less than 5), but not a problem
177 | //with a more useful number of sites.
178 | //TODO: find a way to fix this
179 |
180 | if (closeEnough(edgeCorner1.loc.x, edgeCorner2.loc.x, 1)) {
181 | drawTriangle(g, edgeCorner1, edgeCorner2, c);
182 | } else {
183 | int[] x = new int[4];
184 | int[] y = new int[4];
185 | x[0] = (int) c.loc.x;
186 | y[0] = (int) c.loc.y;
187 | x[1] = (int) edgeCorner1.loc.x;
188 | y[1] = (int) edgeCorner1.loc.y;
189 |
190 | //determine which corner this is
191 | x[2] = (int) ((closeEnough(edgeCorner1.loc.x, bounds.x, 1) || closeEnough(edgeCorner2.loc.x, bounds.x, .5)) ? bounds.x : bounds.right);
192 | y[2] = (int) ((closeEnough(edgeCorner1.loc.y, bounds.y, 1) || closeEnough(edgeCorner2.loc.y, bounds.y, .5)) ? bounds.y : bounds.bottom);
193 |
194 | x[3] = (int) edgeCorner2.loc.x;
195 | y[3] = (int) edgeCorner2.loc.y;
196 |
197 | g.fillPolygon(x, y, 4);
198 | c.area += 0; //TODO: area of polygon given vertices
199 | }
200 | }
201 | }
202 |
203 | //also records the area of each voronoi cell
204 | public void paint(Graphics2D g, boolean drawBiomes, boolean drawRivers, boolean drawSites, boolean drawCorners, boolean drawDelaunay, boolean drawVoronoi) {
205 | final int numSites = centers.size();
206 |
207 | Color[] defaultColors = null;
208 | if (!drawBiomes) {
209 | defaultColors = new Color[numSites];
210 | for (int i = 0; i < defaultColors.length; i++) {
211 | defaultColors[i] = new Color(r.nextInt(255), r.nextInt(255), r.nextInt(255));
212 | }
213 | }
214 |
215 | Graphics2D pixelCenterGraphics = pixelCenterMap.createGraphics();
216 |
217 | //draw via triangles
218 | for (Center c : centers) {
219 | drawPolygon(g, c, drawBiomes ? getColor(c.biome) : defaultColors[c.index]);
220 | drawPolygon(pixelCenterGraphics, c, new Color(c.index));
221 | }
222 |
223 | for (Edge e : edges) {
224 | if (drawDelaunay) {
225 | g.setStroke(new BasicStroke(1));
226 | g.setColor(Color.YELLOW);
227 | g.drawLine((int) e.d0.loc.x, (int) e.d0.loc.y, (int) e.d1.loc.x, (int) e.d1.loc.y);
228 | }
229 | if (drawRivers && e.river > 0) {
230 | g.setStroke(new BasicStroke(1 + (int) Math.sqrt(e.river * 2)));
231 | g.setColor(RIVER);
232 | g.drawLine((int) e.v0.loc.x, (int) e.v0.loc.y, (int) e.v1.loc.x, (int) e.v1.loc.y);
233 | }
234 | }
235 |
236 | if (drawSites) {
237 | g.setColor(Color.BLACK);
238 | centers.stream().forEach((s) -> {
239 | g.fillOval((int) (s.loc.x - 2), (int) (s.loc.y - 2), 4, 4);
240 | });
241 | }
242 |
243 | if (drawCorners) {
244 | g.setColor(Color.WHITE);
245 | corners.stream().forEach((c) -> {
246 | g.fillOval((int) (c.loc.x - 2), (int) (c.loc.y - 2), 4, 4);
247 | });
248 | }
249 | g.setColor(Color.WHITE);
250 | g.drawRect((int) bounds.x, (int) bounds.y, (int) bounds.width, (int) bounds.height);
251 | }
252 |
253 | private void buildGraph(Voronoi v) {
254 | final HashMap pointCenterMap = new HashMap();
255 | final ArrayList points = v.siteCoords();
256 | points.stream().forEach((p) -> {
257 | Center c = new Center();
258 | c.loc = p;
259 | c.index = centers.size();
260 | centers.add(c);
261 | pointCenterMap.put(p, c);
262 | });
263 |
264 | //bug fix
265 | centers.stream().forEach((c) -> {
266 | v.region(c.loc);
267 | });
268 |
269 | final ArrayList libedges = v.edges();
270 | final HashMap pointCornerMap = new HashMap();
271 |
272 | for (com.hoten.delaunay.voronoi.nodename.as3delaunay.Edge libedge : libedges) {
273 | final LineSegment vEdge = libedge.voronoiEdge();
274 | final LineSegment dEdge = libedge.delaunayLine();
275 |
276 | final Edge edge = new Edge();
277 | edge.index = edges.size();
278 | edges.add(edge);
279 |
280 | edge.v0 = makeCorner(pointCornerMap, vEdge.p0);
281 | edge.v1 = makeCorner(pointCornerMap, vEdge.p1);
282 | edge.d0 = pointCenterMap.get(dEdge.p0);
283 | edge.d1 = pointCenterMap.get(dEdge.p1);
284 |
285 | // Centers point to edges. Corners point to edges.
286 | if (edge.d0 != null) {
287 | edge.d0.borders.add(edge);
288 | }
289 | if (edge.d1 != null) {
290 | edge.d1.borders.add(edge);
291 | }
292 | if (edge.v0 != null) {
293 | edge.v0.protrudes.add(edge);
294 | }
295 | if (edge.v1 != null) {
296 | edge.v1.protrudes.add(edge);
297 | }
298 |
299 | // Centers point to centers.
300 | if (edge.d0 != null && edge.d1 != null) {
301 | addToCenterList(edge.d0.neighbors, edge.d1);
302 | addToCenterList(edge.d1.neighbors, edge.d0);
303 | }
304 |
305 | // Corners point to corners
306 | if (edge.v0 != null && edge.v1 != null) {
307 | addToCornerList(edge.v0.adjacent, edge.v1);
308 | addToCornerList(edge.v1.adjacent, edge.v0);
309 | }
310 |
311 | // Centers point to corners
312 | if (edge.d0 != null) {
313 | addToCornerList(edge.d0.corners, edge.v0);
314 | addToCornerList(edge.d0.corners, edge.v1);
315 | }
316 | if (edge.d1 != null) {
317 | addToCornerList(edge.d1.corners, edge.v0);
318 | addToCornerList(edge.d1.corners, edge.v1);
319 | }
320 |
321 | // Corners point to centers
322 | if (edge.v0 != null) {
323 | addToCenterList(edge.v0.touches, edge.d0);
324 | addToCenterList(edge.v0.touches, edge.d1);
325 | }
326 | if (edge.v1 != null) {
327 | addToCenterList(edge.v1.touches, edge.d0);
328 | addToCenterList(edge.v1.touches, edge.d1);
329 | }
330 | }
331 | }
332 |
333 | // Helper functions for the following for loop; ideally these
334 | // would be inlined
335 | private void addToCornerList(ArrayList list, Corner c) {
336 | if (c != null && !list.contains(c)) {
337 | list.add(c);
338 | }
339 | }
340 |
341 | private void addToCenterList(ArrayList list, Center c) {
342 | if (c != null && !list.contains(c)) {
343 | list.add(c);
344 | }
345 | }
346 |
347 | //ensures that each corner is represented by only one corner object
348 | private Corner makeCorner(HashMap pointCornerMap, Point p) {
349 | if (p == null) {
350 | return null;
351 | }
352 | int index = (int) ((int) p.x + (int) (p.y) * bounds.width * 2);
353 | Corner c = pointCornerMap.get(index);
354 | if (c == null) {
355 | c = new Corner();
356 | c.loc = p;
357 | c.border = bounds.liesOnAxes(p);
358 | c.index = corners.size();
359 | corners.add(c);
360 | pointCornerMap.put(index, c);
361 | }
362 | return c;
363 | }
364 |
365 | private void assignCornerElevations(HeightAlgorithm algorithm) {
366 | LinkedList queue = new LinkedList();
367 | for (Corner c : corners) {
368 | c.water = algorithm.isWater(c.loc, bounds, r);
369 | if (c.border) {
370 | c.elevation = 0;
371 | queue.add(c);
372 | } else {
373 | c.elevation = Double.MAX_VALUE;
374 | }
375 | }
376 |
377 | while (!queue.isEmpty()) {
378 | Corner c = queue.pop();
379 | for (Corner a : c.adjacent) {
380 | double newElevation = 0.01 + c.elevation;
381 | if (!c.water && !a.water) {
382 | newElevation += 1;
383 | }
384 | if (newElevation < a.elevation) {
385 | a.elevation = newElevation;
386 | queue.add(a);
387 | }
388 | }
389 | }
390 | }
391 |
392 | private void assignOceanCoastAndLand() {
393 | LinkedList queue = new LinkedList();
394 | final double waterThreshold = .3;
395 | for (final Center center : centers) {
396 | int numWater = 0;
397 | for (final Corner c : center.corners) {
398 | if (c.border) {
399 | center.border = center.water = center.ocean = true;
400 | queue.add(center);
401 | }
402 | if (c.water) {
403 | numWater++;
404 | }
405 | }
406 | center.water = center.ocean || ((double) numWater / center.corners.size() >= waterThreshold);
407 | }
408 | while (!queue.isEmpty()) {
409 | final Center center = queue.pop();
410 | for (final Center n : center.neighbors) {
411 | if (n.water && !n.ocean) {
412 | n.ocean = true;
413 | queue.add(n);
414 | }
415 | }
416 | }
417 | for (Center center : centers) {
418 | boolean oceanNeighbor = false;
419 | boolean landNeighbor = false;
420 | for (Center n : center.neighbors) {
421 | oceanNeighbor |= n.ocean;
422 | landNeighbor |= !n.water;
423 | }
424 | center.coast = oceanNeighbor && landNeighbor;
425 | }
426 |
427 | for (Corner c : corners) {
428 | int numOcean = 0;
429 | int numLand = 0;
430 | for (Center center : c.touches) {
431 | numOcean += center.ocean ? 1 : 0;
432 | numLand += !center.water ? 1 : 0;
433 | }
434 | c.ocean = numOcean == c.touches.size();
435 | c.coast = numOcean > 0 && numLand > 0;
436 | c.water = c.border || ((numLand != c.touches.size()) && !c.coast);
437 | }
438 | }
439 |
440 | private ArrayList landCorners() {
441 | final ArrayList list = new ArrayList();
442 | for (Corner c : corners) {
443 | if (!c.ocean && !c.coast) {
444 | list.add(c);
445 | }
446 | }
447 | return list;
448 | }
449 |
450 | private void redistributeElevations(ArrayList landCorners) {
451 | Collections.sort(landCorners, new Comparator() {
452 | @Override
453 | public int compare(Corner o1, Corner o2) {
454 | if (o1.elevation > o2.elevation) {
455 | return 1;
456 | } else if (o1.elevation < o2.elevation) {
457 | return -1;
458 | }
459 | return 0;
460 | }
461 | });
462 |
463 | final double SCALE_FACTOR = 1.1;
464 | for (int i = 0; i < landCorners.size(); i++) {
465 | double y = (double) i / landCorners.size();
466 | double x = Math.sqrt(SCALE_FACTOR) - Math.sqrt(SCALE_FACTOR * (1 - y));
467 | x = Math.min(x, 1);
468 | landCorners.get(i).elevation = x;
469 | }
470 |
471 | for (Corner c : corners) {
472 | if (c.ocean || c.coast) {
473 | c.elevation = 0.0;
474 | }
475 | }
476 | }
477 |
478 | private void assignPolygonElevations() {
479 | for (Center center : centers) {
480 | double total = 0;
481 | for (Corner c : center.corners) {
482 | total += c.elevation;
483 | }
484 | center.elevation = total / center.corners.size();
485 | }
486 | }
487 |
488 | private void calculateDownslopes() {
489 | for (Corner c : corners) {
490 | Corner down = c;
491 | //System.out.println("ME: " + c.elevation);
492 | for (Corner a : c.adjacent) {
493 | //System.out.println(a.elevation);
494 | if (a.elevation <= down.elevation) {
495 | down = a;
496 | }
497 | }
498 | c.downslope = down;
499 | }
500 | }
501 |
502 | private void createRivers() {
503 | for (int i = 0; i < bounds.width / 2; i++) {
504 | Corner c = corners.get(r.nextInt(corners.size()));
505 | if (c.ocean || c.elevation < 0.3 || c.elevation > 0.9) {
506 | continue;
507 | }
508 | // Bias rivers to go west: if (q.downslope.x > q.x) continue;
509 | while (!c.coast) {
510 | if (c == c.downslope) {
511 | break;
512 | }
513 | Edge edge = lookupEdgeFromCorner(c, c.downslope);
514 | if (!edge.v0.water || !edge.v1.water) {
515 | edge.river++;
516 | c.river++;
517 | c.downslope.river++; // TODO: fix double count
518 | }
519 | c = c.downslope;
520 | }
521 | }
522 | }
523 |
524 | private Edge lookupEdgeFromCorner(Corner c, Corner downslope) {
525 | for (Edge e : c.protrudes) {
526 | if (e.v0 == downslope || e.v1 == downslope) {
527 | return e;
528 | }
529 | }
530 | return null;
531 | }
532 |
533 | private void assignCornerMoisture() {
534 | LinkedList queue = new LinkedList();
535 | for (Corner c : corners) {
536 | if ((c.water || c.river > 0) && !c.ocean) {
537 | c.moisture = c.river > 0 ? Math.min(3.0, (0.2 * c.river)) : 1.0;
538 | queue.push(c);
539 | } else {
540 | c.moisture = 0.0;
541 | }
542 | }
543 |
544 | while (!queue.isEmpty()) {
545 | Corner c = queue.pop();
546 | for (Corner a : c.adjacent) {
547 | double newM = .9 * c.moisture;
548 | if (newM > a.moisture) {
549 | a.moisture = newM;
550 | queue.add(a);
551 | }
552 | }
553 | }
554 |
555 | // Salt water
556 | for (Corner c : corners) {
557 | if (c.ocean || c.coast) {
558 | c.moisture = 1.0;
559 | }
560 | }
561 | }
562 |
563 | private void redistributeMoisture(ArrayList landCorners) {
564 | Collections.sort(landCorners, new Comparator() {
565 | @Override
566 | public int compare(Corner o1, Corner o2) {
567 | if (o1.moisture > o2.moisture) {
568 | return 1;
569 | } else if (o1.moisture < o2.moisture) {
570 | return -1;
571 | }
572 | return 0;
573 | }
574 | });
575 | for (int i = 0; i < landCorners.size(); i++) {
576 | landCorners.get(i).moisture = (double) i / landCorners.size();
577 | }
578 | }
579 |
580 | private void assignPolygonMoisture() {
581 | for (Center center : centers) {
582 | double total = 0;
583 | for (Corner c : center.corners) {
584 | total += c.moisture;
585 | }
586 | center.moisture = total / center.corners.size();
587 | }
588 | }
589 |
590 | private void assignBiomes() {
591 | for (Center center : centers) {
592 | center.biome = getBiome(center);
593 | }
594 | }
595 | }
596 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/groundshapes/Blob.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.groundshapes;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 |
6 | import java.util.Random;
7 |
8 | /**
9 | * A blob with eyes.
10 | */
11 | public class Blob implements HeightAlgorithm {
12 |
13 | /** {@inheritDoc} */
14 | @Override
15 | public boolean isWater(Point point, Rectangle bounds, Random random) {
16 | Point p = new Point(2 * (point.x / bounds.width - 0.5), 2 * (point.y / bounds.height - 0.5));
17 | boolean eye1 = new Point(p.x - 0.2, p.y / 2 + 0.2).length() < 0.05;
18 | boolean eye2 = new Point(p.x + 0.2, p.y / 2 + 0.2).length() < 0.05;
19 | boolean body = p.length() < 0.8 - 0.18 * Math.sin(5 * Math.atan2(p.y, p.x));
20 | return !body || eye1 || eye2;
21 | }
22 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/groundshapes/HeightAlgorithm.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.groundshapes;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 |
6 | import java.util.Random;
7 |
8 | /**
9 | * Use implementation of this interface to find out which points in graph are water and which - ground.
10 | */
11 | public interface HeightAlgorithm {
12 |
13 | /**
14 | * Uses specific algorithm to check point.
15 | *
16 | * @param p Corner location.
17 | * @param bounds Graph bounds.
18 | * @param random Voronoi's randomizer to keep identical results for user's seed.
19 | * @return
20 | */
21 | boolean isWater(Point p, Rectangle bounds, Random random);
22 |
23 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/groundshapes/Perlin.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.groundshapes;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 |
6 | import java.util.Random;
7 |
8 | /**
9 | * Code from article
10 | * How to Use Perlin Noise in Your, GamesHerman Tulleken.
11 | *
12 | * This algorithm do next steps:
13 | *
14 | * - Create white noise as base for perlin noise.
15 | * - Smooth it {@code octaveCount} times.
16 | *
17 | */
18 | public class Perlin implements HeightAlgorithm {
19 |
20 | private final Random r;
21 | private final float median;
22 | private final float noise[][];
23 |
24 | /**
25 | * @param random Randomizer.
26 | * @param octaveCount Smooth value. 0 means there will be only white noise.
27 | * @param noiseWidth Width of noise map. Should be less than graph width.
28 | * @param noiseHeight Height of noise map. Should be less than graph width.
29 | */
30 | public Perlin(Random random, int octaveCount, int noiseWidth, int noiseHeight) {
31 | this.r = random;
32 |
33 | float whiteNoise[][] = generateWhiteNoise(noiseWidth + 1, noiseHeight + 1);
34 |
35 | noise = generatePerlinNoise(whiteNoise, octaveCount);
36 |
37 | median = findMedian();
38 | }
39 |
40 | /**
41 | * @return The value dividing ground and water.
42 | */
43 | private float findMedian() {
44 | long count[] = new long[10];
45 |
46 | for (float[] aNoise : noise) {
47 | for (float aaNoise : aNoise) {
48 | for (int k = 1; k < count.length; k++) {
49 | if (aaNoise * 10 < k) {
50 | count[k - 1]++;
51 | break;
52 | }
53 | }
54 | }
55 | }
56 |
57 | int n = 0;
58 |
59 | for (int i = 1; i < count.length; i++) {
60 | if (count[i] > count[n]) n = i;
61 | }
62 |
63 | return (float) (n + 0.5) / 10;
64 | }
65 |
66 | /**
67 | *
68 | * @param random Randomizer.
69 | * @param octaveCount Smooth value. 0 means there will be only white noise.
70 | */
71 | public Perlin(Random random, float centrical, int octaveCount) {
72 | this(random, octaveCount, 256, 256);
73 | }
74 |
75 | /** {@inheritDoc} */
76 | @Override
77 | public boolean isWater(Point p, Rectangle bounds, Random random) {
78 | int x = (int) (p.x / bounds.width * (noise.length - 1));
79 |
80 | int y = (int) (p.y / bounds.height * (noise[0].length - 1));
81 |
82 | return noise[x][y] < median;
83 | }
84 |
85 | /**
86 | *
87 | * @param width Width of noise map.
88 | * @param height height of noise map.
89 | * @return White noise map.
90 | */
91 | private float[][] generateWhiteNoise(int width, int height) {
92 | float[][] noise = new float[width][height];
93 |
94 | for (int i = width / 25; i < width * 0.96; i++) {
95 | for (int j = height / 25; j < height * 0.96; j++) {
96 | noise[i][j] = r.nextFloat();
97 | }
98 | }
99 |
100 | return noise;
101 | }
102 |
103 | /**
104 | *
105 | * @param baseNoise Noise map which will be processed.
106 | * @param octaveCount Smooth value. 0 means there will be only white noise.
107 | * @return Perlin noise.
108 | */
109 | private float[][] generatePerlinNoise(float[][] baseNoise, int octaveCount) {
110 | int width = baseNoise.length;
111 | int height = baseNoise[0].length;
112 |
113 | float[][][] smoothNoise = new float[octaveCount][][]; //an array of 2D arrays containing
114 |
115 | float persistance = 0.5f;
116 |
117 | //generate smooth noise
118 | for (int i = 0; i < octaveCount; i++) {
119 | smoothNoise[i] = generateSmoothNoise(baseNoise, i);
120 | }
121 |
122 | float[][] perlinNoise = new float[width][height];
123 | float amplitude = 1.0f;
124 | float totalAmplitude = 0.0f;
125 |
126 | //blend noise together
127 | for (int octave = octaveCount - 1; octave >= 0; octave--) {
128 | amplitude *= persistance;
129 | totalAmplitude += amplitude;
130 |
131 | for (int i = 0; i < width; i++) {
132 | for (int j = 0; j < height; j++) {
133 | perlinNoise[i][j] += smoothNoise[octave][i][j] * amplitude;
134 | }
135 | }
136 | }
137 |
138 | //normalisation
139 | for (int i = 0; i < width; i++) {
140 | for (int j = 0; j < height; j++) {
141 | perlinNoise[i][j] /= totalAmplitude;
142 | }
143 | }
144 |
145 | return perlinNoise;
146 | }
147 |
148 | /**
149 | *
150 | * @param baseNoise Noise map which will be used to create a smoother noise map.
151 | * @param octave Smooth value. 0 means there will be only white noise.
152 | * @return Smoother noise map than a base map.
153 | */
154 | private float[][] generateSmoothNoise(float[][] baseNoise, int octave) {
155 | int width = baseNoise.length;
156 | int height = baseNoise[0].length;
157 |
158 | float[][] smoothNoise = new float[width][height];
159 |
160 | int samplePeriod = 1 << octave; // calculates 2 ^ k
161 | float sampleFrequency = 1.0f / samplePeriod;
162 |
163 | for (int i = 0; i < width; i++) {
164 | //calculate the horizontal sampling indices
165 | int sample_i0 = (i / samplePeriod) * samplePeriod;
166 | int sample_i1 = (sample_i0 + samplePeriod) % width; //wrap around
167 | float horizontal_blend = (i - sample_i0) * sampleFrequency;
168 |
169 | for (int j = 0; j < height; j++) {
170 | //calculate the vertical sampling indices
171 | int sample_j0 = (j / samplePeriod) * samplePeriod;
172 | int sample_j1 = (sample_j0 + samplePeriod) % height; //wrap around
173 | float vertical_blend = (j - sample_j0) * sampleFrequency;
174 |
175 | //blend the top two corners
176 | float top = interpolate(baseNoise[sample_i0][sample_j0],
177 | baseNoise[sample_i1][sample_j0], horizontal_blend);
178 |
179 | //blend the bottom two corners
180 | float bottom = interpolate(baseNoise[sample_i0][sample_j1],
181 | baseNoise[sample_i1][sample_j1], horizontal_blend);
182 |
183 | //final blend
184 | smoothNoise[i][j] = interpolate(top, bottom, vertical_blend);
185 | }
186 | }
187 |
188 | return smoothNoise;
189 | }
190 |
191 | /**
192 | * Function returns a linear interpolation between two values.
193 | * Essentially, the closer alpha is to 0, the closer the resulting value will be to x0;
194 | * the closer alpha is to 1, the closer the resulting value will be to x1.
195 | *
196 | * @param x0 First value.
197 | * @param x1 Second value.
198 | * @param alpha Transparency.
199 | * @return Linear interpolation between two values.
200 | */
201 | private static float interpolate(float x0, float x1, float alpha) {
202 | return x0 * (1 - alpha) + alpha * x1;
203 | }
204 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/groundshapes/Radial.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.groundshapes;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 |
6 | import java.util.Random;
7 |
8 | /**
9 | *
10 | */
11 | public class Radial implements HeightAlgorithm {
12 |
13 | private final double ISLAND_FACTOR; // 1.0 means no small islands; 2.0 leads to a lot
14 | private final int bumps;
15 | private final double startAngle;
16 | private final double dipAngle;
17 | private final double dipWidth;
18 |
19 | /**
20 | *
21 | * @param ISLAND_FACTOR 1.0 means no small islands, 2.0 leads to a lot
22 | * @param bumps
23 | * @param startAngle
24 | * @param dipAngle
25 | * @param dipWidth
26 | */
27 | public Radial(double ISLAND_FACTOR, int bumps, double startAngle, double dipAngle, double dipWidth) {
28 | this.ISLAND_FACTOR = ISLAND_FACTOR;
29 | this.bumps = bumps;
30 | this.startAngle = startAngle;
31 | this.dipAngle = dipAngle;
32 | this.dipWidth = dipWidth;
33 | }
34 |
35 | /** {@inheritDoc} */
36 | @Override
37 | public boolean isWater(Point p, Rectangle bounds, Random r) {
38 | p = new Point(2 * (p.x / bounds.width - 0.5), 2 * (p.y / bounds.height - 0.5));
39 |
40 | double angle = Math.atan2(p.y, p.x);
41 | double length = 0.5 * (Math.max(Math.abs(p.x), Math.abs(p.y)) + p.length());
42 |
43 | double r1 = 0.5 + 0.40 * Math.sin(startAngle + bumps * angle + Math.cos((bumps + 3) * angle));
44 | double r2 = 0.7 - 0.20 * Math.sin(startAngle + bumps * angle - Math.sin((bumps + 2) * angle));
45 | if (Math.abs(angle - dipAngle) < dipWidth
46 | || Math.abs(angle - dipAngle + 2 * Math.PI) < dipWidth
47 | || Math.abs(angle - dipAngle - 2 * Math.PI) < dipWidth) {
48 | r1 = r2 = 0.2;
49 | }
50 | return !(length < r1 || (length > r1 * ISLAND_FACTOR && length < r2));
51 | }
52 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Circle.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 |
5 | public final class Circle extends Object {
6 |
7 | public Point center;
8 | public double radius;
9 |
10 | public Circle(double centerX, double centerY, double radius) {
11 | super();
12 | this.center = new Point(centerX, centerY);
13 | this.radius = radius;
14 | }
15 |
16 | @Override
17 | public String toString() {
18 | return "Circle (center: " + center + "; radius: " + radius + ")";
19 | }
20 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Edge.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 | import java.util.HashMap;
6 | import java.util.Stack;
7 |
8 | /**
9 | * The line segment connecting the two Sites is part of the Delaunay
10 | * triangulation; the line segment connecting the two Vertices is part of the
11 | * Voronoi diagram
12 | *
13 | * @author ashaw
14 | *
15 | */
16 | public final class Edge {
17 |
18 | final private static Stack _pool = new Stack();
19 |
20 | /**
21 | * This is the only way to create a new Edge
22 | *
23 | * @param site0
24 | * @param site1
25 | * @return
26 | *
27 | */
28 | public static Edge createBisectingEdge(Site site0, Site site1) {
29 | double dx, dy, absdx, absdy;
30 | double a, b, c;
31 |
32 | dx = site1.get_x() - site0.get_x();
33 | dy = site1.get_y() - site0.get_y();
34 | absdx = dx > 0 ? dx : -dx;
35 | absdy = dy > 0 ? dy : -dy;
36 | c = site0.get_x() * dx + site0.get_y() * dy + (dx * dx + dy * dy) * 0.5;
37 | if (absdx > absdy) {
38 | a = 1.0;
39 | b = dy / dx;
40 | c /= dx;
41 | } else {
42 | b = 1.0;
43 | a = dx / dy;
44 | c /= dy;
45 | }
46 |
47 | Edge edge = Edge.create();
48 |
49 | edge.set_leftSite(site0);
50 | edge.set_rightSite(site1);
51 | site0.addEdge(edge);
52 | site1.addEdge(edge);
53 |
54 | edge._leftVertex = null;
55 | edge._rightVertex = null;
56 |
57 | edge.a = a;
58 | edge.b = b;
59 | edge.c = c;
60 | //trace("createBisectingEdge: a ", edge.a, "b", edge.b, "c", edge.c);
61 |
62 | return edge;
63 | }
64 |
65 | private static Edge create() {
66 | Edge edge;
67 | if (_pool.size() > 0) {
68 | edge = _pool.pop();
69 | edge.init();
70 | } else {
71 | edge = new Edge();
72 | }
73 | return edge;
74 | }
75 |
76 | /*final private static LINESPRITE:Sprite = new Sprite();
77 | final private static GRAPHICS:Graphics = LINESPRITE.graphics;
78 |
79 | private var _delaunayLineBmp:BitmapData;
80 | internal function get delaunayLineBmp():BitmapData
81 | {
82 | if (!_delaunayLineBmp)
83 | {
84 | _delaunayLineBmp = makeDelaunayLineBmp();
85 | }
86 | return _delaunayLineBmp;
87 | }
88 |
89 | // making this available to Voronoi; running out of memory in AIR so I cannot cache the bmp
90 | internal function makeDelaunayLineBmp():BitmapData
91 | {
92 | var p0:Point = leftSite.coord;
93 | var p1:Point = rightSite.coord;
94 |
95 | GRAPHICS.clear();
96 | // clear() resets line style back to undefined!
97 | GRAPHICS.lineStyle(0, 0, 1.0, false, LineScaleMode.NONE, CapsStyle.NONE);
98 | GRAPHICS.moveTo(p0.x, p0.y);
99 | GRAPHICS.lineTo(p1.x, p1.y);
100 |
101 | var w:int = int(Math.ceil(Math.max(p0.x, p1.x)));
102 | if (w < 1)
103 | {
104 | w = 1;
105 | }
106 | var h:int = int(Math.ceil(Math.max(p0.y, p1.y)));
107 | if (h < 1)
108 | {
109 | h = 1;
110 | }
111 | var bmp:BitmapData = new BitmapData(w, h, true, 0);
112 | bmp.draw(LINESPRITE);
113 | return bmp;
114 | }*/
115 | public LineSegment delaunayLine() {
116 | // draw a line connecting the input Sites for which the edge is a bisector:
117 | return new LineSegment(get_leftSite().get_coord(), get_rightSite().get_coord());
118 | }
119 |
120 | public LineSegment voronoiEdge() {
121 | if (!get_visible()) {
122 | return new LineSegment(null, null);
123 | }
124 | return new LineSegment(_clippedVertices.get(LR.LEFT),
125 | _clippedVertices.get(LR.RIGHT));
126 | }
127 | private static int _nedges = 0;
128 | final public static Edge DELETED = new Edge();
129 | // the equation of the edge: ax + by = c
130 | public double a, b, c;
131 | // the two Voronoi vertices that the edge connects
132 | // (if one of them is null, the edge extends to infinity)
133 | private Vertex _leftVertex;
134 |
135 | public Vertex get_leftVertex() {
136 | return _leftVertex;
137 | }
138 | private Vertex _rightVertex;
139 |
140 | public Vertex get_rightVertex() {
141 | return _rightVertex;
142 | }
143 |
144 | public Vertex vertex(LR leftRight) {
145 | return (leftRight == LR.LEFT) ? _leftVertex : _rightVertex;
146 | }
147 |
148 | public void setVertex(LR leftRight, Vertex v) {
149 | if (leftRight == LR.LEFT) {
150 | _leftVertex = v;
151 | } else {
152 | _rightVertex = v;
153 | }
154 | }
155 |
156 | public boolean isPartOfConvexHull() {
157 | return (_leftVertex == null || _rightVertex == null);
158 | }
159 |
160 | public double sitesDistance() {
161 | return Point.distance(get_leftSite().get_coord(), get_rightSite().get_coord());
162 | }
163 |
164 | public static double compareSitesDistances_MAX(Edge edge0, Edge edge1) {
165 | double length0 = edge0.sitesDistance();
166 | double length1 = edge1.sitesDistance();
167 | if (length0 < length1) {
168 | return 1;
169 | }
170 | if (length0 > length1) {
171 | return -1;
172 | }
173 | return 0;
174 | }
175 |
176 | public static double compareSitesDistances(Edge edge0, Edge edge1) {
177 | return -compareSitesDistances_MAX(edge0, edge1);
178 | }
179 | // Once clipVertices() is called, this Dictionary will hold two Points
180 | // representing the clipped coordinates of the left and right ends...
181 | private HashMap _clippedVertices;
182 |
183 | public HashMap get_clippedEnds() {
184 | return _clippedVertices;
185 | }
186 | // unless the entire Edge is outside the bounds.
187 | // In that case visible will be false:
188 |
189 | public boolean get_visible() {
190 | return _clippedVertices != null;
191 | }
192 | // the two input Sites for which this Edge is a bisector:
193 | private HashMap _sites;
194 |
195 | public void set_leftSite(Site s) {
196 | _sites.put(LR.LEFT, s);
197 | }
198 |
199 | public Site get_leftSite() {
200 | return _sites.get(LR.LEFT);
201 | }
202 |
203 | public void set_rightSite(Site s) {
204 | _sites.put(LR.RIGHT, s);
205 | }
206 |
207 | public Site get_rightSite() {
208 | return _sites.get(LR.RIGHT);
209 | }
210 |
211 | public Site site(LR leftRight) {
212 | return _sites.get(leftRight);
213 | }
214 | private int _edgeIndex;
215 |
216 | public void dispose() {
217 | /*if (_delaunayLineBmp)
218 | {
219 | _delaunayLineBmp.dispose();
220 | _delaunayLineBmp = null;
221 | }*/
222 | _leftVertex = null;
223 | _rightVertex = null;
224 | if (_clippedVertices != null) {
225 | _clippedVertices.clear();
226 | _clippedVertices = null;
227 | }
228 | _sites.clear();
229 | _sites = null;
230 |
231 | _pool.push(this);
232 | }
233 |
234 | private Edge() {
235 | _edgeIndex = _nedges++;
236 | init();
237 | }
238 |
239 | private void init() {
240 | _sites = new HashMap();
241 | }
242 |
243 | public String toString() {
244 | return "Edge " + _edgeIndex + "; sites " + _sites.get(LR.LEFT) + ", " + _sites.get(LR.RIGHT)
245 | + "; endVertices " + (_leftVertex != null ? _leftVertex.get_vertexIndex() : "null") + ", "
246 | + (_rightVertex != null ? _rightVertex.get_vertexIndex() : "null") + "::";
247 | }
248 |
249 | /**
250 | * Set _clippedVertices to contain the two ends of the portion of the
251 | * Voronoi edge that is visible within the bounds. If no part of the Edge
252 | * falls within the bounds, leave _clippedVertices null.
253 | *
254 | * @param bounds
255 | *
256 | */
257 | public void clipVertices(Rectangle bounds) {
258 | double xmin = bounds.x;
259 | double ymin = bounds.y;
260 | double xmax = bounds.right;
261 | double ymax = bounds.bottom;
262 |
263 | Vertex vertex0, vertex1;
264 | double x0, x1, y0, y1;
265 |
266 | if (a == 1.0 && b >= 0.0) {
267 | vertex0 = _rightVertex;
268 | vertex1 = _leftVertex;
269 | } else {
270 | vertex0 = _leftVertex;
271 | vertex1 = _rightVertex;
272 | }
273 |
274 | if (a == 1.0) {
275 | y0 = ymin;
276 | if (vertex0 != null && vertex0.get_y() > ymin) {
277 | y0 = vertex0.get_y();
278 | }
279 | if (y0 > ymax) {
280 | return;
281 | }
282 | x0 = c - b * y0;
283 |
284 | y1 = ymax;
285 | if (vertex1 != null && vertex1.get_y() < ymax) {
286 | y1 = vertex1.get_y();
287 | }
288 | if (y1 < ymin) {
289 | return;
290 | }
291 | x1 = c - b * y1;
292 |
293 | if ((x0 > xmax && x1 > xmax) || (x0 < xmin && x1 < xmin)) {
294 | return;
295 | }
296 |
297 | if (x0 > xmax) {
298 | x0 = xmax;
299 | y0 = (c - x0) / b;
300 | } else if (x0 < xmin) {
301 | x0 = xmin;
302 | y0 = (c - x0) / b;
303 | }
304 |
305 | if (x1 > xmax) {
306 | x1 = xmax;
307 | y1 = (c - x1) / b;
308 | } else if (x1 < xmin) {
309 | x1 = xmin;
310 | y1 = (c - x1) / b;
311 | }
312 | } else {
313 | x0 = xmin;
314 | if (vertex0 != null && vertex0.get_x() > xmin) {
315 | x0 = vertex0.get_x();
316 | }
317 | if (x0 > xmax) {
318 | return;
319 | }
320 | y0 = c - a * x0;
321 |
322 | x1 = xmax;
323 | if (vertex1 != null && vertex1.get_x() < xmax) {
324 | x1 = vertex1.get_x();
325 | }
326 | if (x1 < xmin) {
327 | return;
328 | }
329 | y1 = c - a * x1;
330 |
331 | if ((y0 > ymax && y1 > ymax) || (y0 < ymin && y1 < ymin)) {
332 | return;
333 | }
334 |
335 | if (y0 > ymax) {
336 | y0 = ymax;
337 | x0 = (c - y0) / a;
338 | } else if (y0 < ymin) {
339 | y0 = ymin;
340 | x0 = (c - y0) / a;
341 | }
342 |
343 | if (y1 > ymax) {
344 | y1 = ymax;
345 | x1 = (c - y1) / a;
346 | } else if (y1 < ymin) {
347 | y1 = ymin;
348 | x1 = (c - y1) / a;
349 | }
350 | }
351 |
352 | _clippedVertices = new HashMap();
353 | if (vertex0 == _leftVertex) {
354 | _clippedVertices.put(LR.LEFT, new Point(x0, y0));
355 | _clippedVertices.put(LR.RIGHT, new Point(x1, y1));
356 | } else {
357 | _clippedVertices.put(LR.RIGHT, new Point(x0, y0));
358 | _clippedVertices.put(LR.LEFT, new Point(x1, y1));
359 | }
360 | }
361 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/EdgeList.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.ArrayList;
5 |
6 | public final class EdgeList implements IDisposable {
7 |
8 | private double _deltax;
9 | private double _xmin;
10 | private int _hashsize;
11 | private ArrayList _hash;
12 | public Halfedge leftEnd;
13 | public Halfedge rightEnd;
14 |
15 | @Override
16 | public void dispose() {
17 | Halfedge halfEdge = leftEnd;
18 | Halfedge prevHe;
19 | while (halfEdge != rightEnd) {
20 | prevHe = halfEdge;
21 | halfEdge = halfEdge.edgeListRightNeighbor;
22 | prevHe.dispose();
23 | }
24 | leftEnd = null;
25 | rightEnd.dispose();
26 | rightEnd = null;
27 |
28 | _hash.clear();
29 | _hash = null;
30 | }
31 |
32 | public EdgeList(double xmin, double deltax, int sqrt_nsites) {
33 | _xmin = xmin;
34 | _deltax = deltax;
35 | _hashsize = 2 * sqrt_nsites;
36 |
37 | _hash = new ArrayList(_hashsize);
38 |
39 | // two dummy Halfedges:
40 | leftEnd = Halfedge.createDummy();
41 | rightEnd = Halfedge.createDummy();
42 | leftEnd.edgeListLeftNeighbor = null;
43 | leftEnd.edgeListRightNeighbor = rightEnd;
44 | rightEnd.edgeListLeftNeighbor = leftEnd;
45 | rightEnd.edgeListRightNeighbor = null;
46 |
47 | for(int i = 0; i < _hashsize; i++){
48 | _hash.add(null);
49 | }
50 |
51 | _hash.set(0, leftEnd);
52 | _hash.set(_hashsize - 1, rightEnd);
53 | }
54 |
55 | /**
56 | * Insert newHalfedge to the right of lb
57 | *
58 | * @param lb
59 | * @param newHalfedge
60 | *
61 | */
62 | public void insert(Halfedge lb, Halfedge newHalfedge) {
63 | newHalfedge.edgeListLeftNeighbor = lb;
64 | newHalfedge.edgeListRightNeighbor = lb.edgeListRightNeighbor;
65 | lb.edgeListRightNeighbor.edgeListLeftNeighbor = newHalfedge;
66 | lb.edgeListRightNeighbor = newHalfedge;
67 | }
68 |
69 | /**
70 | * This function only removes the Halfedge from the left-right list. We
71 | * cannot dispose it yet because we are still using it.
72 | *
73 | * @param halfEdge
74 | *
75 | */
76 | public void remove(Halfedge halfEdge) {
77 | halfEdge.edgeListLeftNeighbor.edgeListRightNeighbor = halfEdge.edgeListRightNeighbor;
78 | halfEdge.edgeListRightNeighbor.edgeListLeftNeighbor = halfEdge.edgeListLeftNeighbor;
79 | halfEdge.edge = Edge.DELETED;
80 | halfEdge.edgeListLeftNeighbor = halfEdge.edgeListRightNeighbor = null;
81 | }
82 |
83 | /**
84 | * Find the rightmost Halfedge that is still left of p
85 | *
86 | * @param p
87 | * @return
88 | *
89 | */
90 | public Halfedge edgeListLeftNeighbor(Point p) {
91 | int i, bucket;
92 | Halfedge halfEdge;
93 |
94 | /* Use hash table to get close to desired halfedge */
95 | bucket = (int) ((p.x - _xmin) / _deltax * _hashsize);
96 | if (bucket < 0) {
97 | bucket = 0;
98 | }
99 | if (bucket >= _hashsize) {
100 | bucket = _hashsize - 1;
101 | }
102 | halfEdge = getHash(bucket);
103 | if (halfEdge == null) {
104 | for (i = 1; true; ++i) {
105 | if ((halfEdge = getHash(bucket - i)) != null) {
106 | break;
107 | }
108 | if ((halfEdge = getHash(bucket + i)) != null) {
109 | break;
110 | }
111 | }
112 | }
113 | /* Now search linear list of halfedges for the correct one */
114 | if (halfEdge == leftEnd || (halfEdge != rightEnd && halfEdge.isLeftOf(p))) {
115 | do {
116 | halfEdge = halfEdge.edgeListRightNeighbor;
117 | } while (halfEdge != rightEnd && halfEdge.isLeftOf(p));
118 | halfEdge = halfEdge.edgeListLeftNeighbor;
119 | } else {
120 | do {
121 | halfEdge = halfEdge.edgeListLeftNeighbor;
122 | } while (halfEdge != leftEnd && !halfEdge.isLeftOf(p));
123 | }
124 |
125 | /* Update hash table and reference counts */
126 | if (bucket > 0 && bucket < _hashsize - 1) {
127 | _hash.set(bucket, halfEdge);
128 | }
129 | return halfEdge;
130 | }
131 |
132 | /* Get entry from hash table, pruning any deleted nodes */
133 | private Halfedge getHash(int b) {
134 | Halfedge halfEdge;
135 |
136 | if (b < 0 || b >= _hashsize) {
137 | return null;
138 | }
139 | halfEdge = _hash.get(b);
140 | if (halfEdge != null && halfEdge.edge == Edge.DELETED) {
141 | /* Hash table points to deleted halfedge. Patch as necessary. */
142 | _hash.set(b, null);
143 | // still can't dispose halfEdge yet!
144 | return null;
145 | } else {
146 | return halfEdge;
147 | }
148 | }
149 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/EdgeReorderer.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import java.util.ArrayList;
4 |
5 | public final class EdgeReorderer {
6 |
7 | private ArrayList _edges;
8 | private ArrayList _edgeOrientations;
9 |
10 | public ArrayList get_edges() {
11 | return _edges;
12 | }
13 |
14 | public ArrayList get_edgeOrientations() {
15 | return _edgeOrientations;
16 | }
17 |
18 | public EdgeReorderer(ArrayList origEdges, Class criterion) {
19 | if (criterion != Vertex.class && criterion != Site.class) {
20 | throw new Error("Edges: criterion must be Vertex or Site");
21 | }
22 | _edges = new ArrayList();
23 | _edgeOrientations = new ArrayList();
24 | if (origEdges.size() > 0) {
25 | _edges = reorderEdges(origEdges, criterion);
26 | }
27 | }
28 |
29 | public void dispose() {
30 | _edges = null;
31 | _edgeOrientations = null;
32 | }
33 |
34 | private ArrayList reorderEdges(ArrayList origEdges, Class criterion) {
35 | int i, j;
36 | int n = origEdges.size();
37 | Edge edge;
38 | // we're going to reorder the edges in order of traversal
39 | ArrayList done = new ArrayList(n);
40 | int nDone = 0;
41 | for (int k = 0; k < n; k++) {
42 | done.add( false);
43 | }
44 | ArrayList newEdges = new ArrayList();
45 |
46 | i = 0;
47 | edge = origEdges.get(i);
48 | newEdges.add(edge);
49 | _edgeOrientations.add(LR.LEFT);
50 | ICoord firstPoint = (criterion == Vertex.class) ? edge.get_leftVertex() : edge.get_leftSite();
51 | ICoord lastPoint = (criterion == Vertex.class) ? edge.get_rightVertex() : edge.get_rightSite();
52 |
53 | if (firstPoint == Vertex.VERTEX_AT_INFINITY || lastPoint == Vertex.VERTEX_AT_INFINITY) {
54 | return new ArrayList();
55 | }
56 |
57 | done.set(i, true);
58 | ++nDone;
59 |
60 | while (nDone < n) {
61 | for (i = 1; i < n; ++i) {
62 | if (done.get(i)) {
63 | continue;
64 | }
65 | edge = origEdges.get(i);
66 | ICoord leftPoint = (criterion == Vertex.class) ? edge.get_leftVertex() : edge.get_leftSite();
67 | ICoord rightPoint = (criterion == Vertex.class) ? edge.get_rightVertex() : edge.get_rightSite();
68 | if (leftPoint == Vertex.VERTEX_AT_INFINITY || rightPoint == Vertex.VERTEX_AT_INFINITY) {
69 | return new ArrayList();
70 | }
71 | if (leftPoint == lastPoint) {
72 | lastPoint = rightPoint;
73 | _edgeOrientations.add(LR.LEFT);
74 | newEdges.add(edge);
75 | done.set(i, true);
76 | } else if (rightPoint == firstPoint) {
77 | firstPoint = leftPoint;
78 | _edgeOrientations.add(0, LR.LEFT);
79 | newEdges.add(0, edge);
80 | done.set(i, true);
81 | } else if (leftPoint == firstPoint) {
82 | firstPoint = rightPoint;
83 | _edgeOrientations.add(0, LR.RIGHT);
84 | newEdges.add(0, edge);
85 |
86 | done.set(i, true);
87 | } else if (rightPoint == lastPoint) {
88 | lastPoint = leftPoint;
89 | _edgeOrientations.add(LR.RIGHT);
90 | newEdges.add(edge);
91 | done.set(i, true);
92 | }
93 | if (done.get(i)) {
94 | ++nDone;
95 | }
96 | }
97 | }
98 |
99 | return newEdges;
100 | }
101 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Halfedge.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.Stack;
5 |
6 | public final class Halfedge {
7 |
8 | private static Stack _pool = new Stack();
9 |
10 | public static Halfedge create(Edge edge, LR lr) {
11 | if (_pool.size() > 0) {
12 | return _pool.pop().init(edge, lr);
13 | } else {
14 | return new Halfedge(edge, lr);
15 | }
16 | }
17 |
18 | public static Halfedge createDummy() {
19 | return create(null, null);
20 | }
21 | public Halfedge edgeListLeftNeighbor, edgeListRightNeighbor;
22 | public Halfedge nextInPriorityQueue;
23 | public Edge edge;
24 | public LR leftRight;
25 | public Vertex vertex;
26 | // the vertex's y-coordinate in the transformed Voronoi space V*
27 | public double ystar;
28 |
29 | public Halfedge( Edge edge, LR lr) {
30 | init(edge, lr);
31 | }
32 |
33 | private Halfedge init(Edge edge, LR lr) {
34 | this.edge = edge;
35 | leftRight = lr;
36 | nextInPriorityQueue = null;
37 | vertex = null;
38 | return this;
39 | }
40 |
41 | @Override
42 | public String toString() {
43 | return "Halfedge (leftRight: " + leftRight + "; vertex: " + vertex + ")";
44 | }
45 |
46 | public void dispose() {
47 | if (edgeListLeftNeighbor != null || edgeListRightNeighbor != null) {
48 | // still in EdgeList
49 | return;
50 | }
51 | if (nextInPriorityQueue != null) {
52 | // still in PriorityQueue
53 | return;
54 | }
55 | edge = null;
56 | leftRight = null;
57 | vertex = null;
58 | _pool.push(this);
59 | }
60 |
61 | public void reallyDispose() {
62 | edgeListLeftNeighbor = null;
63 | edgeListRightNeighbor = null;
64 | nextInPriorityQueue = null;
65 | edge = null;
66 | leftRight = null;
67 | vertex = null;
68 | _pool.push(this);
69 | }
70 |
71 | public boolean isLeftOf(Point p) {
72 | Site topSite;
73 | boolean rightOfSite, above, fast;
74 | double dxp, dyp, dxs, t1, t2, t3, yl;
75 |
76 | topSite = edge.get_rightSite();
77 | rightOfSite = p.x > topSite.get_x();
78 | if (rightOfSite && this.leftRight == LR.LEFT) {
79 | return true;
80 | }
81 | if (!rightOfSite && this.leftRight == LR.RIGHT) {
82 | return false;
83 | }
84 |
85 | if (edge.a == 1.0) {
86 | dyp = p.y - topSite.get_y();
87 | dxp = p.x - topSite.get_x();
88 | fast = false;
89 | if ((!rightOfSite && edge.b < 0.0) || (rightOfSite && edge.b >= 0.0)) {
90 | above = dyp >= edge.b * dxp;
91 | fast = above;
92 | } else {
93 | above = p.x + p.y * edge.b > edge.c;
94 | if (edge.b < 0.0) {
95 | above = !above;
96 | }
97 | if (!above) {
98 | fast = true;
99 | }
100 | }
101 | if (!fast) {
102 | dxs = topSite.get_x() - edge.get_leftSite().get_x();
103 | above = edge.b * (dxp * dxp - dyp * dyp)
104 | < dxs * dyp * (1.0 + 2.0 * dxp / dxs + edge.b * edge.b);
105 | if (edge.b < 0.0) {
106 | above = !above;
107 | }
108 | }
109 | } else /* edge.b == 1.0 */ {
110 | yl = edge.c - edge.a * p.x;
111 | t1 = p.y - yl;
112 | t2 = p.x - topSite.get_x();
113 | t3 = yl - topSite.get_y();
114 | above = t1 * t1 > t2 * t2 + t3 * t3;
115 | }
116 | return this.leftRight == LR.LEFT ? above : !above;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/HalfedgePriorityQueue.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.ArrayList;
5 |
6 | public final class HalfedgePriorityQueue // also known as heap
7 | {
8 |
9 | private ArrayList _hash;
10 | private int _count;
11 | private int _minBucket;
12 | private int _hashsize;
13 | private double _ymin;
14 | private double _deltay;
15 |
16 | public HalfedgePriorityQueue(double ymin, double deltay, int sqrt_nsites) {
17 | _ymin = ymin;
18 | _deltay = deltay;
19 | _hashsize = 4 * sqrt_nsites;
20 | initialize();
21 | }
22 |
23 | public void dispose() {
24 | // get rid of dummies
25 | for (int i = 0; i < _hashsize; ++i) {
26 | _hash.get(i).dispose();
27 | }
28 | _hash.clear();
29 | _hash = null;
30 | }
31 |
32 | private void initialize() {
33 | int i;
34 |
35 | _count = 0;
36 | _minBucket = 0;
37 | _hash = new ArrayList(_hashsize);
38 | // dummy Halfedge at the top of each hash
39 | for (i = 0; i < _hashsize; ++i) {
40 | _hash.add(Halfedge.createDummy());
41 | _hash.get(i).nextInPriorityQueue = null;
42 | }
43 | }
44 |
45 | public void insert(Halfedge halfEdge) {
46 | Halfedge previous, next;
47 | int insertionBucket = bucket(halfEdge);
48 | if (insertionBucket < _minBucket) {
49 | _minBucket = insertionBucket;
50 | }
51 | previous = _hash.get(insertionBucket);
52 | while ((next = previous.nextInPriorityQueue) != null
53 | && (halfEdge.ystar > next.ystar || (halfEdge.ystar == next.ystar && halfEdge.vertex.get_x() > next.vertex.get_x()))) {
54 | previous = next;
55 | }
56 | halfEdge.nextInPriorityQueue = previous.nextInPriorityQueue;
57 | previous.nextInPriorityQueue = halfEdge;
58 | ++_count;
59 | }
60 |
61 | public void remove(Halfedge halfEdge) {
62 | Halfedge previous;
63 | int removalBucket = bucket(halfEdge);
64 |
65 | if (halfEdge.vertex != null) {
66 | previous = _hash.get(removalBucket);
67 | while (previous.nextInPriorityQueue != halfEdge) {
68 | previous = previous.nextInPriorityQueue;
69 | }
70 | previous.nextInPriorityQueue = halfEdge.nextInPriorityQueue;
71 | _count--;
72 | halfEdge.vertex = null;
73 | halfEdge.nextInPriorityQueue = null;
74 | halfEdge.dispose();
75 | }
76 | }
77 |
78 | private int bucket(Halfedge halfEdge) {
79 | int theBucket = (int) ((halfEdge.ystar - _ymin) / _deltay * _hashsize);
80 | if (theBucket < 0) {
81 | theBucket = 0;
82 | }
83 | if (theBucket >= _hashsize) {
84 | theBucket = _hashsize - 1;
85 | }
86 | return theBucket;
87 | }
88 |
89 | private boolean isEmpty(int bucket) {
90 | return (_hash.get(bucket).nextInPriorityQueue == null);
91 | }
92 |
93 | /**
94 | * move _minBucket until it contains an actual Halfedge (not just the dummy
95 | * at the top);
96 | *
97 | */
98 | private void adjustMinBucket() {
99 | while (_minBucket < _hashsize - 1 && isEmpty(_minBucket)) {
100 | ++_minBucket;
101 | }
102 | }
103 |
104 | public boolean empty() {
105 | return _count == 0;
106 | }
107 |
108 | /**
109 | * @return coordinates of the Halfedge's vertex in V*, the transformed
110 | * Voronoi diagram
111 | *
112 | */
113 | public Point min() {
114 | adjustMinBucket();
115 | Halfedge answer = _hash.get(_minBucket).nextInPriorityQueue;
116 | return new Point(answer.vertex.get_x(), answer.ystar);
117 | }
118 |
119 | /**
120 | * remove and return the min Halfedge
121 | *
122 | * @return
123 | *
124 | */
125 | public Halfedge extractMin() {
126 | Halfedge answer;
127 |
128 | // get the first real Halfedge in _minBucket
129 | answer = _hash.get(_minBucket).nextInPriorityQueue;
130 |
131 | _hash.get(_minBucket).nextInPriorityQueue = answer.nextInPriorityQueue;
132 | _count--;
133 | answer.nextInPriorityQueue = null;
134 |
135 | return answer;
136 | }
137 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/ICoord.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 |
5 | public interface ICoord {
6 |
7 | Point get_coord();
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/IDisposable.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | public interface IDisposable {
4 |
5 | void dispose();
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/LR.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | public final class LR {
4 |
5 | final public static LR LEFT = new LR( "left");
6 | final public static LR RIGHT = new LR( "right");
7 | private String _name;
8 |
9 | public LR(String name) {
10 | _name = name;
11 | }
12 |
13 | public static LR other(LR leftRight) {
14 | return leftRight == LEFT ? RIGHT : LEFT;
15 | }
16 |
17 | @Override
18 | public String toString() {
19 | return _name;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/LineSegment.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 |
5 | public final class LineSegment extends Object {
6 |
7 | public static double compareLengths_MAX(LineSegment segment0, LineSegment segment1) {
8 | double length0 = Point.distance(segment0.p0, segment0.p1);
9 | double length1 = Point.distance(segment1.p0, segment1.p1);
10 | if (length0 < length1) {
11 | return 1;
12 | }
13 | if (length0 > length1) {
14 | return -1;
15 | }
16 | return 0;
17 | }
18 |
19 | public static double compareLengths(LineSegment edge0, LineSegment edge1) {
20 | return -compareLengths_MAX(edge0, edge1);
21 | }
22 | public Point p0, p1;
23 |
24 | public LineSegment(Point p0, Point p1) {
25 | super();
26 | this.p0 = p0;
27 | this.p1 = p1;
28 | }
29 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Polygon.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.ArrayList;
5 |
6 | public final class Polygon {
7 |
8 | private ArrayList _vertices;
9 |
10 | public Polygon(ArrayList vertices) {
11 | _vertices = vertices;
12 | }
13 |
14 | public double area() {
15 | return Math.abs(signedDoubleArea() * 0.5);
16 | }
17 |
18 | public Winding winding() {
19 | double signedDoubleArea = signedDoubleArea();
20 | if (signedDoubleArea < 0) {
21 | return Winding.CLOCKWISE;
22 | }
23 | if (signedDoubleArea > 0) {
24 | return Winding.COUNTERCLOCKWISE;
25 | }
26 | return Winding.NONE;
27 | }
28 |
29 | private double signedDoubleArea() {
30 | int index, nextIndex;
31 | int n = _vertices.size();
32 | Point point, next;
33 | double signedDoubleArea = 0;
34 | for (index = 0; index < n; ++index) {
35 | nextIndex = (index + 1) % n;
36 | point = _vertices.get(index);
37 | next = _vertices.get(nextIndex);
38 | signedDoubleArea += point.x * next.y - next.x * point.y;
39 | }
40 | return signedDoubleArea;
41 | }
42 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Site.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 | import java.awt.Color;
6 | import java.util.ArrayList;
7 | import java.util.Collections;
8 | import java.util.Comparator;
9 | import java.util.Stack;
10 |
11 | public final class Site implements ICoord {
12 |
13 | private static Stack _pool = new Stack();
14 |
15 | public static Site create(Point p, int index, double weight, Color color) {
16 | if (_pool.size() > 0) {
17 | return _pool.pop().init(p, index, weight, color);
18 | } else {
19 | return new Site(p, index, weight, color);
20 | }
21 | }
22 |
23 | public static void sortSites(ArrayList sites) {
24 | //sites.sort(Site.compare);
25 | Collections.sort(sites, new Comparator() {
26 | @Override
27 | public int compare(Site o1, Site o2) {
28 | return (int) Site.compare(o1, o2);
29 | }
30 | });
31 | }
32 |
33 | /**
34 | * sort sites on y, then x, coord also change each site's _siteIndex to
35 | * match its new position in the list so the _siteIndex can be used to
36 | * identify the site for nearest-neighbor queries
37 | *
38 | * haha "also" - means more than one responsibility...
39 | *
40 | */
41 | private static double compare(Site s1, Site s2) {
42 | int returnValue = Voronoi.compareByYThenX(s1, s2);
43 |
44 | // swap _siteIndex values if necessary to match new ordering:
45 | int tempIndex;
46 | if (returnValue == -1) {
47 | if (s1._siteIndex > s2._siteIndex) {
48 | tempIndex = s1._siteIndex;
49 | s1._siteIndex = s2._siteIndex;
50 | s2._siteIndex = tempIndex;
51 | }
52 | } else if (returnValue == 1) {
53 | if (s2._siteIndex > s1._siteIndex) {
54 | tempIndex = s2._siteIndex;
55 | s2._siteIndex = s1._siteIndex;
56 | s1._siteIndex = tempIndex;
57 | }
58 |
59 | }
60 |
61 | return returnValue;
62 | }
63 | final private static double EPSILON = .005;
64 |
65 | private static boolean closeEnough(Point p0, Point p1) {
66 | return Point.distance(p0, p1) < EPSILON;
67 | }
68 | private Point _coord;
69 |
70 | @Override
71 | public Point get_coord() {
72 | return _coord;
73 | }
74 | public Color color;
75 | public double weight;
76 | private int _siteIndex;
77 | // the edges that define this Site's Voronoi region:
78 | public ArrayList _edges;
79 | // which end of each edge hooks up with the previous edge in _edges:
80 | private ArrayList _edgeOrientations;
81 | // ordered list of points that define the region clipped to bounds:
82 | private ArrayList _region;
83 |
84 | public Site(Point p, int index, double weight, Color color) {
85 | init(p, index, weight, color);
86 | }
87 |
88 | private Site init(Point p, int index, double weight, Color color) {
89 | _coord = p;
90 | _siteIndex = index;
91 | this.weight = weight;
92 | this.color = color;
93 | _edges = new ArrayList();
94 | _region = null;
95 | return this;
96 | }
97 |
98 | @Override
99 | public String toString() {
100 | return "Site " + _siteIndex + ": " + get_coord();
101 | }
102 |
103 | private void move(Point p) {
104 | clear();
105 | _coord = p;
106 | }
107 |
108 | public void dispose() {
109 | _coord = null;
110 | clear();
111 | _pool.push(this);
112 | }
113 |
114 | private void clear() {
115 | if (_edges != null) {
116 | _edges.clear();
117 | _edges = null;
118 | }
119 | if (_edgeOrientations != null) {
120 | _edgeOrientations.clear();
121 | _edgeOrientations = null;
122 | }
123 | if (_region != null) {
124 | _region.clear();
125 | _region = null;
126 | }
127 | }
128 |
129 | void addEdge(Edge edge) {
130 | _edges.add(edge);
131 | }
132 |
133 | public Edge nearestEdge() {
134 | // _edges.sort(Edge.compareSitesDistances);
135 | Collections.sort(_edges, new Comparator() {
136 | @Override
137 | public int compare(Edge o1, Edge o2) {
138 | return (int) Edge.compareSitesDistances(o1, o2);
139 | }
140 | });
141 | return _edges.get(0);
142 | }
143 |
144 | ArrayList neighborSites() {
145 | if (_edges == null || _edges.isEmpty()) {
146 | return new ArrayList();
147 | }
148 | if (_edgeOrientations == null) {
149 | reorderEdges();
150 | }
151 | ArrayList list = new ArrayList();
152 | for (Edge edge : _edges) {
153 | list.add(neighborSite(edge));
154 | }
155 | return list;
156 | }
157 |
158 | private Site neighborSite(Edge edge) {
159 | if (this == edge.get_leftSite()) {
160 | return edge.get_rightSite();
161 | }
162 | if (this == edge.get_rightSite()) {
163 | return edge.get_leftSite();
164 | }
165 | return null;
166 | }
167 |
168 | ArrayList region(Rectangle clippingBounds) {
169 | if (_edges == null || _edges.isEmpty()) {
170 | return new ArrayList();
171 | }
172 | if (_edgeOrientations == null) {
173 | reorderEdges();
174 | _region = clipToBounds(clippingBounds);
175 | if ((new Polygon(_region)).winding() == Winding.CLOCKWISE) {
176 | Collections.reverse(_region);
177 | }
178 | }
179 | return _region;
180 | }
181 |
182 | private void reorderEdges() {
183 | //trace("_edges:", _edges);
184 | EdgeReorderer reorderer = new EdgeReorderer(_edges, Vertex.class);
185 | _edges = reorderer.get_edges();
186 | //trace("reordered:", _edges);
187 | _edgeOrientations = reorderer.get_edgeOrientations();
188 | reorderer.dispose();
189 | }
190 |
191 | private ArrayList clipToBounds(Rectangle bounds) {
192 | ArrayList points = new ArrayList();
193 | int n = _edges.size();
194 | int i = 0;
195 | Edge edge;
196 | while (i < n && (_edges.get(i).get_visible() == false)) {
197 | ++i;
198 | }
199 |
200 | if (i == n) {
201 | // no edges visible
202 | return new ArrayList();
203 | }
204 | edge = _edges.get(i);
205 | LR orientation = _edgeOrientations.get(i);
206 | points.add(edge.get_clippedEnds().get(orientation));
207 | points.add(edge.get_clippedEnds().get((LR.other(orientation))));
208 |
209 | for (int j = i + 1; j < n; ++j) {
210 | edge = _edges.get(j);
211 | if (edge.get_visible() == false) {
212 | continue;
213 | }
214 | connect(points, j, bounds, false);
215 | }
216 | // close up the polygon by adding another corner point of the bounds if needed:
217 | connect(points, i, bounds, true);
218 |
219 | return points;
220 | }
221 |
222 | private void connect(ArrayList points, int j, Rectangle bounds, boolean closingUp) {
223 | Point rightPoint = points.get(points.size() - 1);
224 | Edge newEdge = _edges.get(j);
225 | LR newOrientation = _edgeOrientations.get(j);
226 | // the point that must be connected to rightPoint:
227 | Point newPoint = newEdge.get_clippedEnds().get(newOrientation);
228 | if (!closeEnough(rightPoint, newPoint)) {
229 | // The points do not coincide, so they must have been clipped at the bounds;
230 | // see if they are on the same border of the bounds:
231 | if (rightPoint.x != newPoint.x
232 | && rightPoint.y != newPoint.y) {
233 | // They are on different borders of the bounds;
234 | // insert one or two corners of bounds as needed to hook them up:
235 | // (NOTE this will not be correct if the region should take up more than
236 | // half of the bounds rect, for then we will have gone the wrong way
237 | // around the bounds and included the smaller part rather than the larger)
238 | int rightCheck = BoundsCheck.check(rightPoint, bounds);
239 | int newCheck = BoundsCheck.check(newPoint, bounds);
240 | double px, py;
241 | if ((rightCheck & BoundsCheck.RIGHT) != 0) {
242 | px = bounds.right;
243 | if ((newCheck & BoundsCheck.BOTTOM) != 0) {
244 | py = bounds.bottom;
245 | points.add(new Point(px, py));
246 | } else if ((newCheck & BoundsCheck.TOP) != 0) {
247 | py = bounds.top;
248 | points.add(new Point(px, py));
249 | } else if ((newCheck & BoundsCheck.LEFT) != 0) {
250 | if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height) {
251 | py = bounds.top;
252 | } else {
253 | py = bounds.bottom;
254 | }
255 | points.add(new Point(px, py));
256 | points.add(new Point(bounds.left, py));
257 | }
258 | } else if ((rightCheck & BoundsCheck.LEFT) != 0) {
259 | px = bounds.left;
260 | if ((newCheck & BoundsCheck.BOTTOM) != 0) {
261 | py = bounds.bottom;
262 | points.add(new Point(px, py));
263 | } else if ((newCheck & BoundsCheck.TOP) != 0) {
264 | py = bounds.top;
265 | points.add(new Point(px, py));
266 | } else if ((newCheck & BoundsCheck.RIGHT) != 0) {
267 | if (rightPoint.y - bounds.y + newPoint.y - bounds.y < bounds.height) {
268 | py = bounds.top;
269 | } else {
270 | py = bounds.bottom;
271 | }
272 | points.add(new Point(px, py));
273 | points.add(new Point(bounds.right, py));
274 | }
275 | } else if ((rightCheck & BoundsCheck.TOP) != 0) {
276 | py = bounds.top;
277 | if ((newCheck & BoundsCheck.RIGHT) != 0) {
278 | px = bounds.right;
279 | points.add(new Point(px, py));
280 | } else if ((newCheck & BoundsCheck.LEFT) != 0) {
281 | px = bounds.left;
282 | points.add(new Point(px, py));
283 | } else if ((newCheck & BoundsCheck.BOTTOM) != 0) {
284 | if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width) {
285 | px = bounds.left;
286 | } else {
287 | px = bounds.right;
288 | }
289 | points.add(new Point(px, py));
290 | points.add(new Point(px, bounds.bottom));
291 | }
292 | } else if ((rightCheck & BoundsCheck.BOTTOM) != 0) {
293 | py = bounds.bottom;
294 | if ((newCheck & BoundsCheck.RIGHT) != 0) {
295 | px = bounds.right;
296 | points.add(new Point(px, py));
297 | } else if ((newCheck & BoundsCheck.LEFT) != 0) {
298 | px = bounds.left;
299 | points.add(new Point(px, py));
300 | } else if ((newCheck & BoundsCheck.TOP) != 0) {
301 | if (rightPoint.x - bounds.x + newPoint.x - bounds.x < bounds.width) {
302 | px = bounds.left;
303 | } else {
304 | px = bounds.right;
305 | }
306 | points.add(new Point(px, py));
307 | points.add(new Point(px, bounds.top));
308 | }
309 | }
310 | }
311 | if (closingUp) {
312 | // newEdge's ends have already been added
313 | return;
314 | }
315 | points.add(newPoint);
316 | }
317 | Point newRightPoint = newEdge.get_clippedEnds().get(LR.other(newOrientation));
318 | if (!closeEnough(points.get(0), newRightPoint)) {
319 | points.add(newRightPoint);
320 | }
321 | }
322 |
323 | public double get_x() {
324 | return _coord.x;
325 | }
326 |
327 | public double get_y() {
328 | return _coord.y;
329 | }
330 |
331 | public double dist(ICoord p) {
332 | return Point.distance(p.get_coord(), this._coord);
333 | }
334 | }
335 |
336 | final class BoundsCheck {
337 |
338 | final public static int TOP = 1;
339 | final public static int BOTTOM = 2;
340 | final public static int LEFT = 4;
341 | final public static int RIGHT = 8;
342 |
343 | /**
344 | *
345 | * @param point
346 | * @param bounds
347 | * @return an int with the appropriate bits set if the Point lies on the
348 | * corresponding bounds lines
349 | *
350 | */
351 | public static int check(Point point, Rectangle bounds) {
352 | int value = 0;
353 | if (point.x == bounds.left) {
354 | value |= LEFT;
355 | }
356 | if (point.x == bounds.right) {
357 | value |= RIGHT;
358 | }
359 | if (point.y == bounds.top) {
360 | value |= TOP;
361 | }
362 | if (point.y == bounds.bottom) {
363 | value |= BOTTOM;
364 | }
365 | return value;
366 | }
367 |
368 | public BoundsCheck() {
369 | throw new Error("BoundsCheck constructor unused");
370 | }
371 | }
372 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/SiteList.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import com.hoten.delaunay.geom.Rectangle;
5 | import java.util.ArrayList;
6 |
7 | public final class SiteList implements IDisposable {
8 |
9 | private ArrayList _sites;
10 | private int _currentIndex;
11 | private boolean _sorted;
12 |
13 | public SiteList() {
14 | _sites = new ArrayList();
15 | _sorted = false;
16 | }
17 |
18 | @Override
19 | public void dispose() {
20 | if (_sites != null) {
21 | for (Site site : _sites) {
22 | site.dispose();
23 | }
24 | _sites.clear();
25 | _sites = null;
26 | }
27 | }
28 |
29 | public int push(Site site) {
30 | _sorted = false;
31 | _sites.add(site);
32 | return _sites.size();
33 | }
34 |
35 | public int get_length() {
36 | return _sites.size();
37 | }
38 |
39 | public Site next() {
40 | if (_sorted == false) {
41 | throw new Error("SiteList::next(): sites have not been sorted");
42 | }
43 | if (_currentIndex < _sites.size()) {
44 | return _sites.get(_currentIndex++);
45 | } else {
46 | return null;
47 | }
48 | }
49 |
50 | public Rectangle getSitesBounds() {
51 | if (_sorted == false) {
52 | Site.sortSites(_sites);
53 | _currentIndex = 0;
54 | _sorted = true;
55 | }
56 | double xmin, xmax, ymin, ymax;
57 | if (_sites.isEmpty()) {
58 | return new Rectangle(0, 0, 0, 0);
59 | }
60 | xmin = Double.MAX_VALUE;
61 | xmax = Double.MIN_VALUE;
62 | for (Site site : _sites) {
63 | if (site.get_x() < xmin) {
64 | xmin = site.get_x();
65 | }
66 | if (site.get_x() > xmax) {
67 | xmax = site.get_x();
68 | }
69 | }
70 | // here's where we assume that the sites have been sorted on y:
71 | ymin = _sites.get(0).get_y();
72 | ymax = _sites.get(_sites.size() - 1).get_y();
73 |
74 | return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
75 | }
76 |
77 | /*public ArrayList siteColors(referenceImage:BitmapData = null)
78 | {
79 | var colors:Vector. = new Vector.();
80 | for each (var site:Site in _sites)
81 | {
82 | colors.push(referenceImage ? referenceImage.getPixel(site.x, site.y) : site.color);
83 | }
84 | return colors;
85 | }*/
86 | public ArrayList siteCoords() {
87 | ArrayList coords = new ArrayList();
88 | for (Site site : _sites) {
89 | coords.add(site.get_coord());
90 | }
91 | return coords;
92 | }
93 |
94 | /**
95 | *
96 | * @return the largest circle centered at each site that fits in its region;
97 | * if the region is infinite, return a circle of radius 0.
98 | *
99 | */
100 | public ArrayList circles() {
101 | ArrayList circles = new ArrayList();
102 | for (Site site : _sites) {
103 | double radius = 0;
104 | Edge nearestEdge = site.nearestEdge();
105 |
106 | //!nearestEdge.isPartOfConvexHull() && (radius = nearestEdge.sitesDistance() * 0.5);
107 | if (!nearestEdge.isPartOfConvexHull()) {
108 | radius = nearestEdge.sitesDistance() * 0.5;
109 | }
110 | circles.add(new Circle(site.get_x(), site.get_y(), radius));
111 | }
112 | return circles;
113 | }
114 |
115 | public ArrayList> regions(Rectangle plotBounds) {
116 | ArrayList> regions = new ArrayList();
117 | for (Site site : _sites) {
118 | regions.add(site.region(plotBounds));
119 | }
120 | return regions;
121 | }
122 | /**
123 | *
124 | * @param proximityMap a BitmapData whose regions are filled with the site
125 | * index values; see PlanePointsCanvas::fillRegions()
126 | * @param x
127 | * @param y
128 | * @return coordinates of nearest Site to (x, y)
129 | *
130 | */
131 | /*public Point nearestSitePoint(proximityMap:BitmapData, double x, double y)
132 | {
133 | var index:uint = proximityMap.getPixel(x, y);
134 | if (index > _sites.length - 1)
135 | {
136 | return null;
137 | }
138 | return _sites[index].coord;
139 | }*/
140 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Triangle.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import java.util.ArrayList;
4 |
5 | public final class Triangle {
6 |
7 | private ArrayList _sites;
8 |
9 | public ArrayList get_sites() {
10 | return _sites;
11 | }
12 |
13 | public Triangle(Site a, Site b, Site c) {
14 | _sites = new ArrayList();
15 | _sites.add(a);
16 | _sites.add(b);
17 | _sites.add(c);
18 | }
19 |
20 | public void dispose() {
21 | _sites.clear();
22 | _sites = null;
23 | }
24 | }
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Vertex.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | import com.hoten.delaunay.geom.Point;
4 | import java.util.Stack;
5 |
6 | final public class Vertex extends Object implements ICoord {
7 |
8 | final public static Vertex VERTEX_AT_INFINITY = new Vertex(Double.NaN, Double.NaN);
9 | final private static Stack _pool = new Stack();
10 |
11 | private static Vertex create(double x, double y) {
12 |
13 | if (Double.isNaN(x) || Double.isNaN(y)) {
14 | return VERTEX_AT_INFINITY;
15 | }
16 | if (_pool.size() > 0) {
17 |
18 | return _pool.pop().init(x, y);
19 | } else {
20 | return new Vertex(x, y);
21 | }
22 | }
23 | private static int _nvertices = 0;
24 | private Point _coord;
25 |
26 | @Override
27 | public Point get_coord() {
28 | return _coord;
29 | }
30 | private int _vertexIndex;
31 |
32 | public int get_vertexIndex() {
33 | return _vertexIndex;
34 | }
35 |
36 | public Vertex(double x, double y) {
37 | init(x, y);
38 | }
39 |
40 | private Vertex init(double x, double y) {
41 | _coord = new Point(x, y);
42 | return this;
43 | }
44 |
45 | public void dispose() {
46 | _coord = null;
47 | _pool.push(this);
48 | }
49 |
50 | public void setIndex() {
51 | _vertexIndex = _nvertices++;
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | return "Vertex (" + _vertexIndex + ")";
57 | }
58 |
59 | /**
60 | * This is the only way to make a Vertex
61 | *
62 | * @param halfedge0
63 | * @param halfedge1
64 | * @return
65 | *
66 | */
67 | public static Vertex intersect(Halfedge halfedge0, Halfedge halfedge1) {
68 | Edge edge0, edge1, edge;
69 | Halfedge halfedge;
70 | double determinant, intersectionX, intersectionY;
71 | boolean rightOfSite;
72 |
73 | edge0 = halfedge0.edge;
74 | edge1 = halfedge1.edge;
75 | if (edge0 == null || edge1 == null) {
76 | return null;
77 | }
78 | if (edge0.get_rightSite() == edge1.get_rightSite()) {
79 | return null;
80 | }
81 |
82 | determinant = edge0.a * edge1.b - edge0.b * edge1.a;
83 | if (-1.0e-10 < determinant && determinant < 1.0e-10) {
84 | // the edges are parallel
85 | return null;
86 | }
87 |
88 | intersectionX = (edge0.c * edge1.b - edge1.c * edge0.b) / determinant;
89 | intersectionY = (edge1.c * edge0.a - edge0.c * edge1.a) / determinant;
90 |
91 | if (Voronoi.compareByYThenX(edge0.get_rightSite(), edge1.get_rightSite()) < 0) {
92 | halfedge = halfedge0;
93 | edge = edge0;
94 | } else {
95 | halfedge = halfedge1;
96 | edge = edge1;
97 | }
98 | rightOfSite = intersectionX >= edge.get_rightSite().get_x();
99 | if ((rightOfSite && halfedge.leftRight == LR.LEFT)
100 | || (!rightOfSite && halfedge.leftRight == LR.RIGHT)) {
101 | return null;
102 | }
103 |
104 | return Vertex.create(intersectionX, intersectionY);
105 | }
106 |
107 | public double get_x() {
108 | return _coord.x;
109 | }
110 |
111 | public double get_y() {
112 | return _coord.y;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Voronoi.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | /*
4 | * Java implementaition by Connor Clark (www.hotengames.com). Pretty much a 1:1
5 | * translation of a wonderful map generating algorthim by Amit Patel of Red Blob Games,
6 | * which can be found here (http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/)
7 | * Hopefully it's of use to someone out there who needed it in Java like I did!
8 | * Note, the only island mode implemented is Radial. Implementing more is something for another day.
9 | *
10 | * FORTUNE'S ALGORTIHIM
11 | *
12 | * This is a java implementation of an AS3 (Flash) implementation of an algorthim
13 | * originally created in C++. Pretty much a 1:1 translation from as3 to java, save
14 | * for some necessary workarounds. Original as3 implementation by Alan Shaw (of nodename)
15 | * can be found here (https://github.com/nodename/as3delaunay). Original algorthim
16 | * by Steven Fortune (see lisence for c++ implementation below)
17 | *
18 | * The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
19 | * Bell Laboratories.
20 | * Permission to use, copy, modify, and distribute this software for any
21 | * purpose without fee is hereby granted, provided that this entire notice
22 | * is included in all copies of any software which is or includes a copy
23 | * or modification of this software and in all copies of the supporting
24 | * documentation for such software.
25 | * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
26 | * WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
27 | * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
28 | * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
29 | */
30 |
31 | import com.hoten.delaunay.geom.Point;
32 | import com.hoten.delaunay.geom.Rectangle;
33 |
34 | import java.awt.Color;
35 | import java.util.ArrayList;
36 | import java.util.HashMap;
37 | import java.util.Random;
38 |
39 | public final class Voronoi {
40 |
41 | private SiteList _sites;
42 | private HashMap _sitesIndexedByLocation;
43 | private ArrayList _triangles;
44 | private ArrayList _edges;
45 | // TODO generalize this so it doesn't have to be a rectangle;
46 | // then we can make the fractal voronois-within-voronois
47 | private Rectangle _plotBounds;
48 |
49 | public Rectangle get_plotBounds() {
50 | return _plotBounds;
51 | }
52 |
53 | public void dispose() {
54 | int i, n;
55 | if (_sites != null) {
56 | _sites.dispose();
57 | _sites = null;
58 | }
59 | if (_triangles != null) {
60 | n = _triangles.size();
61 | for (i = 0; i < n; ++i) {
62 | _triangles.get(i).dispose();
63 | }
64 | _triangles.clear();
65 | _triangles = null;
66 | }
67 | if (_edges != null) {
68 | n = _edges.size();
69 | for (i = 0; i < n; ++i) {
70 | _edges.get(i).dispose();
71 | }
72 | _edges.clear();
73 | _edges = null;
74 | }
75 | _plotBounds = null;
76 | _sitesIndexedByLocation = null;
77 | }
78 |
79 | public Voronoi(ArrayList points, ArrayList colors, Rectangle plotBounds) {
80 | init(points, colors, plotBounds);
81 | fortunesAlgorithm();
82 | }
83 |
84 | //TODO check points for negative values. We must search min value too and translate whole list to fit it in positive quadrant.
85 | public Voronoi(ArrayList points, ArrayList colors) {
86 | double maxWidth = 0, maxHeight = 0;
87 | for (Point p : points) {
88 | maxWidth = Math.max(maxWidth, p.x);
89 | maxHeight = Math.max(maxHeight, p.y);
90 | }
91 | System.out.println(maxWidth + "," + maxHeight);
92 | init(points, colors, new Rectangle(0, 0, maxWidth, maxHeight));
93 | fortunesAlgorithm();
94 | }
95 |
96 | public Voronoi(int numSites, double maxWidth, double maxHeight, Random r, ArrayList colors) {
97 | ArrayList points = new ArrayList();
98 | for (int i = 0; i < numSites; i++) {
99 | points.add(new Point(r.nextDouble() * maxWidth, r.nextDouble() * maxHeight));
100 | }
101 | init(points, colors, new Rectangle(0, 0, maxWidth, maxHeight));
102 | fortunesAlgorithm();
103 | }
104 |
105 | private void init(ArrayList points, ArrayList colors, Rectangle plotBounds) {
106 | _sites = new SiteList();
107 | _sitesIndexedByLocation = new HashMap();
108 | addSites(points, colors);
109 | _plotBounds = plotBounds;
110 | _triangles = new ArrayList();
111 | _edges = new ArrayList();
112 | }
113 |
114 | private void addSites(ArrayList points, ArrayList colors) {
115 | int length = points.size();
116 | for (int i = 0; i < length; ++i) {
117 | addSite(points.get(i), colors != null ? colors.get(i) : null, i);
118 | }
119 | }
120 |
121 | private void addSite(Point p, Color color, int index) {
122 | double weight = Math.random() * 100;
123 | Site site = Site.create(p, index, weight, color);
124 | _sites.push(site);
125 | _sitesIndexedByLocation.put(p, site);
126 | }
127 |
128 | public ArrayList edges() {
129 | return _edges;
130 | }
131 |
132 | public ArrayList region(Point p) {
133 | Site site = _sitesIndexedByLocation.get(p);
134 | if (site == null) {
135 | return new ArrayList();
136 | }
137 | return site.region(_plotBounds);
138 | }
139 |
140 | // TODO: bug: if you call this before you call region(), something goes wrong :(
141 | public ArrayList neighborSitesForSite(Point coord) {
142 | ArrayList points = new ArrayList();
143 | Site site = _sitesIndexedByLocation.get(coord);
144 | if (site == null) {
145 | return points;
146 | }
147 | ArrayList sites = site.neighborSites();
148 | for (Site neighbor : sites) {
149 | points.add(neighbor.get_coord());
150 | }
151 | return points;
152 | }
153 |
154 | public ArrayList circles() {
155 | return _sites.circles();
156 | }
157 |
158 | private ArrayList selectEdgesForSitePoint(Point coord, ArrayList edgesToTest) {
159 | ArrayList filtered = new ArrayList();
160 |
161 | for (Edge e : edgesToTest) {
162 | if (((e.get_leftSite() != null && e.get_leftSite().get_coord() == coord)
163 | || (e.get_rightSite() != null && e.get_rightSite().get_coord() == coord))) {
164 | filtered.add(e);
165 | }
166 | }
167 | return filtered;
168 |
169 | /*function myTest(edge:Edge, index:int, vector:Vector.):Boolean
170 | {
171 | return ((edge.leftSite && edge.leftSite.coord == coord)
172 | || (edge.rightSite && edge.rightSite.coord == coord));
173 | }*/
174 | }
175 |
176 | private ArrayList visibleLineSegments(ArrayList edges) {
177 | ArrayList segments = new ArrayList();
178 |
179 | for (Edge edge : edges) {
180 | if (edge.get_visible()) {
181 | Point p1 = edge.get_clippedEnds().get(LR.LEFT);
182 | Point p2 = edge.get_clippedEnds().get(LR.RIGHT);
183 | segments.add(new LineSegment(p1, p2));
184 | }
185 | }
186 |
187 | return segments;
188 | }
189 |
190 | private ArrayList delaunayLinesForEdges(ArrayList edges) {
191 | ArrayList segments = new ArrayList();
192 |
193 | for (Edge edge : edges) {
194 | segments.add(edge.delaunayLine());
195 | }
196 |
197 | return segments;
198 | }
199 |
200 | public ArrayList voronoiBoundaryForSite(Point coord) {
201 | return visibleLineSegments(selectEdgesForSitePoint(coord, _edges));
202 | }
203 |
204 | public ArrayList delaunayLinesForSite(Point coord) {
205 | return delaunayLinesForEdges(selectEdgesForSitePoint(coord, _edges));
206 | }
207 |
208 | public ArrayList voronoiDiagram() {
209 | return visibleLineSegments(_edges);
210 | }
211 |
212 | /*public ArrayList delaunayTriangulation(keepOutMask:BitmapData = null)
213 | {
214 | return delaunayLinesForEdges(selectNonIntersectingEdges(keepOutMask, _edges));
215 | }*/
216 | public ArrayList hull() {
217 | return delaunayLinesForEdges(hullEdges());
218 | }
219 |
220 | private ArrayList hullEdges() {
221 | ArrayList filtered = new ArrayList();
222 |
223 | for (Edge e : _edges) {
224 | if (e.isPartOfConvexHull()) {
225 | filtered.add(e);
226 | }
227 | }
228 |
229 |
230 |
231 | return filtered;
232 |
233 | /*function myTest(edge:Edge, index:int, vector:Vector.):Boolean
234 | {
235 | return (edge.isPartOfConvexHull());
236 | }*/
237 | }
238 |
239 | public ArrayList hullPointsInOrder() {
240 | ArrayList hullEdges = hullEdges();
241 |
242 | ArrayList points = new ArrayList();
243 | if (hullEdges.isEmpty()) {
244 | return points;
245 | }
246 |
247 | EdgeReorderer reorderer = new EdgeReorderer(hullEdges, Site.class);
248 | hullEdges = reorderer.get_edges();
249 | ArrayList orientations = reorderer.get_edgeOrientations();
250 | reorderer.dispose();
251 |
252 | LR orientation;
253 |
254 | int n = hullEdges.size();
255 | for (int i = 0; i < n; ++i) {
256 | Edge edge = hullEdges.get(i);
257 | orientation = orientations.get(i);
258 | points.add(edge.site(orientation).get_coord());
259 | }
260 | return points;
261 | }
262 |
263 | /*public ArrayList spanningTree(String type, keepOutMask:BitmapData = null)
264 | {
265 | ArrayList edges = selectNonIntersectingEdges(keepOutMask, _edges);
266 | ArrayList segments = delaunayLinesForEdges(edges);
267 | return kruskal(segments, type);
268 | }*/
269 | public ArrayList> regions() {
270 | return _sites.regions(_plotBounds);
271 | }
272 |
273 | /*public ArrayList siteColors(referenceImage:BitmapData = null)
274 | {
275 | return _sites.siteColors(referenceImage);
276 | }*/
277 | /**
278 | *
279 | * @param proximityMap a BitmapData whose regions are filled with the site
280 | * index values; see PlanePointsCanvas::fillRegions()
281 | * @param x
282 | * @param y
283 | * @return coordinates of nearest Site to (x, y)
284 | *
285 | */
286 | /*public Point nearestSitePoint(proximityMap:BitmapData,double x, double y)
287 | {
288 | return _sites.nearestSitePoint(proximityMap, x, y);
289 | }*/
290 | public ArrayList siteCoords() {
291 | return _sites.siteCoords();
292 | }
293 |
294 | private void fortunesAlgorithm() {
295 | Site newSite, bottomSite, topSite, tempSite;
296 | Vertex v, vertex;
297 | Point newintstar = null;
298 | LR leftRight;
299 | Halfedge lbnd, rbnd, llbnd, rrbnd, bisector;
300 | Edge edge;
301 |
302 | Rectangle dataBounds = _sites.getSitesBounds();
303 |
304 | int sqrt_nsites = (int) Math.sqrt(_sites.get_length() + 4);
305 | HalfedgePriorityQueue heap = new HalfedgePriorityQueue(dataBounds.y, dataBounds.height, sqrt_nsites);
306 | EdgeList edgeList = new EdgeList(dataBounds.x, dataBounds.width, sqrt_nsites);
307 | ArrayList halfEdges = new ArrayList();
308 | ArrayList vertices = new ArrayList();
309 |
310 | Site bottomMostSite = _sites.next();
311 | newSite = _sites.next();
312 |
313 | for (;;) {
314 | if (heap.empty() == false) {
315 | newintstar = heap.min();
316 | }
317 |
318 | if (newSite != null
319 | && (heap.empty() || compareByYThenX(newSite, newintstar) < 0)) {
320 | /* new site is smallest */
321 | //trace("smallest: new site " + newSite);
322 |
323 | // Step 8:
324 | lbnd = edgeList.edgeListLeftNeighbor(newSite.get_coord()); // the Halfedge just to the left of newSite
325 | //trace("lbnd: " + lbnd);
326 | rbnd = lbnd.edgeListRightNeighbor; // the Halfedge just to the right
327 | //trace("rbnd: " + rbnd);
328 | bottomSite = rightRegion(lbnd, bottomMostSite); // this is the same as leftRegion(rbnd)
329 | // this Site determines the region containing the new site
330 | //trace("new Site is in region of existing site: " + bottomSite);
331 |
332 | // Step 9:
333 | edge = Edge.createBisectingEdge(bottomSite, newSite);
334 | //trace("new edge: " + edge);
335 | _edges.add(edge);
336 |
337 | bisector = Halfedge.create(edge, LR.LEFT);
338 | halfEdges.add(bisector);
339 | // inserting two Halfedges into edgeList constitutes Step 10:
340 | // insert bisector to the right of lbnd:
341 | edgeList.insert(lbnd, bisector);
342 |
343 | // first half of Step 11:
344 | if ((vertex = Vertex.intersect(lbnd, bisector)) != null) {
345 | vertices.add(vertex);
346 | heap.remove(lbnd);
347 | lbnd.vertex = vertex;
348 | lbnd.ystar = vertex.get_y() + newSite.dist(vertex);
349 | heap.insert(lbnd);
350 | }
351 |
352 | lbnd = bisector;
353 | bisector = Halfedge.create(edge, LR.RIGHT);
354 | halfEdges.add(bisector);
355 | // second Halfedge for Step 10:
356 | // insert bisector to the right of lbnd:
357 | edgeList.insert(lbnd, bisector);
358 |
359 | // second half of Step 11:
360 | if ((vertex = Vertex.intersect(bisector, rbnd)) != null) {
361 | vertices.add(vertex);
362 | bisector.vertex = vertex;
363 | bisector.ystar = vertex.get_y() + newSite.dist(vertex);
364 | heap.insert(bisector);
365 | }
366 |
367 | newSite = _sites.next();
368 | } else if (heap.empty() == false) {
369 | /* intersection is smallest */
370 | lbnd = heap.extractMin();
371 | llbnd = lbnd.edgeListLeftNeighbor;
372 | rbnd = lbnd.edgeListRightNeighbor;
373 | rrbnd = rbnd.edgeListRightNeighbor;
374 | bottomSite = leftRegion(lbnd, bottomMostSite);
375 | topSite = rightRegion(rbnd, bottomMostSite);
376 | // these three sites define a Delaunay triangle
377 | // (not actually using these for anything...)
378 | //_triangles.push(new Triangle(bottomSite, topSite, rightRegion(lbnd)));
379 |
380 | v = lbnd.vertex;
381 | v.setIndex();
382 | lbnd.edge.setVertex(lbnd.leftRight, v);
383 | rbnd.edge.setVertex(rbnd.leftRight, v);
384 | edgeList.remove(lbnd);
385 | heap.remove(rbnd);
386 | edgeList.remove(rbnd);
387 | leftRight = LR.LEFT;
388 | if (bottomSite.get_y() > topSite.get_y()) {
389 | tempSite = bottomSite;
390 | bottomSite = topSite;
391 | topSite = tempSite;
392 | leftRight = LR.RIGHT;
393 | }
394 | edge = Edge.createBisectingEdge(bottomSite, topSite);
395 | _edges.add(edge);
396 | bisector = Halfedge.create(edge, leftRight);
397 | halfEdges.add(bisector);
398 | edgeList.insert(llbnd, bisector);
399 | edge.setVertex(LR.other(leftRight), v);
400 | if ((vertex = Vertex.intersect(llbnd, bisector)) != null) {
401 | vertices.add(vertex);
402 | heap.remove(llbnd);
403 | llbnd.vertex = vertex;
404 | llbnd.ystar = vertex.get_y() + bottomSite.dist(vertex);
405 | heap.insert(llbnd);
406 | }
407 | if ((vertex = Vertex.intersect(bisector, rrbnd)) != null) {
408 | vertices.add(vertex);
409 | bisector.vertex = vertex;
410 | bisector.ystar = vertex.get_y() + bottomSite.dist(vertex);
411 | heap.insert(bisector);
412 | }
413 | } else {
414 | break;
415 | }
416 | }
417 |
418 | // heap should be empty now
419 | heap.dispose();
420 | edgeList.dispose();
421 |
422 | for (Halfedge halfEdge : halfEdges) {
423 | halfEdge.reallyDispose();
424 | }
425 | halfEdges.clear();
426 |
427 | // we need the vertices to clip the edges
428 | for (Edge e : _edges) {
429 | e.clipVertices(_plotBounds);
430 | }
431 | // but we don't actually ever use them again!
432 | for (Vertex v0 : vertices) {
433 | v0.dispose();
434 | }
435 | vertices.clear();
436 |
437 |
438 | }
439 |
440 | Site leftRegion(Halfedge he, Site bottomMostSite) {
441 | Edge edge = he.edge;
442 | if (edge == null) {
443 | return bottomMostSite;
444 | }
445 | return edge.site(he.leftRight);
446 | }
447 |
448 | Site rightRegion(Halfedge he, Site bottomMostSite) {
449 | Edge edge = he.edge;
450 | if (edge == null) {
451 | return bottomMostSite;
452 | }
453 | return edge.site(LR.other(he.leftRight));
454 | }
455 |
456 | public static int compareByYThenX(Site s1, Site s2) {
457 | if (s1.get_y() < s2.get_y()) {
458 | return -1;
459 | }
460 | if (s1.get_y() > s2.get_y()) {
461 | return 1;
462 | }
463 | if (s1.get_x() < s2.get_x()) {
464 | return -1;
465 | }
466 | if (s1.get_x() > s2.get_x()) {
467 | return 1;
468 | }
469 | return 0;
470 | }
471 |
472 | public static int compareByYThenX(Site s1, Point s2) {
473 | if (s1.get_y() < s2.y) {
474 | return -1;
475 | }
476 | if (s1.get_y() > s2.y) {
477 | return 1;
478 | }
479 | if (s1.get_x() < s2.x) {
480 | return -1;
481 | }
482 | if (s1.get_x() > s2.x) {
483 | return 1;
484 | }
485 | return 0;
486 | }
487 | }
488 |
--------------------------------------------------------------------------------
/src/main/java/com/hoten/delaunay/voronoi/nodename/as3delaunay/Winding.java:
--------------------------------------------------------------------------------
1 | package com.hoten.delaunay.voronoi.nodename.as3delaunay;
2 |
3 | public final class Winding {
4 |
5 | final public static Winding CLOCKWISE = new Winding("clockwise");
6 | final public static Winding COUNTERCLOCKWISE = new Winding("counterclockwise");
7 | final public static Winding NONE = new Winding("none");
8 | private String _name;
9 |
10 | private Winding(String name) {
11 | super();
12 | _name = name;
13 | }
14 |
15 | @Override
16 | public String toString() {
17 | return _name;
18 | }
19 | }
--------------------------------------------------------------------------------