queue = Lists.newArrayList();
43 | Random random = new Random(System.currentTimeMillis());
44 | locateStrategy.initialize(samples, function.getBoundingShape().getBounds2D());
45 |
46 | // Generate and add first sample
47 | Vector firstSample = null;
48 | while (firstSample == null || !locateStrategy.addSample(firstSample)) {
49 | firstSample = function.createSampleIn(function.getBoundingShape());
50 | }
51 | queue.add(firstSample);
52 | samples.add(firstSample);
53 |
54 | while (!queue.isEmpty()) {
55 | // Get random element from the queue.
56 | int queueIndex = random.nextInt(queue.size());
57 | Vector sample = queue.get(queueIndex);
58 |
59 | // Attempt to a create new valid sample near the existing sample.
60 | Vector newValidSample = createNewValidSample(function, sample);
61 |
62 | // Add the new valid sample or remove the existing sample from the queue.
63 | if (newValidSample != null && locateStrategy.addSample(newValidSample)) {
64 | queue.add(newValidSample);
65 | samples.add(newValidSample);
66 | } else {
67 | queue.remove(queueIndex);
68 | }
69 | }
70 | return this;
71 | }
72 |
73 | private Vector createNewValidSample(SampleFunction function, Vector sample) {
74 | for (int i = 0; i < maxTries; i++) {
75 | Vector newSample = function.createSampleNear(sample);
76 | if (isValid(function, newSample)) {
77 | return newSample;
78 | }
79 | }
80 | return null;
81 | }
82 |
83 | private boolean isValid(SampleFunction function, final Vector v) {
84 | Vector nearest = locateStrategy.getNearest(v);
85 | if (nearest == null) {
86 | return false;
87 | }
88 |
89 | Rectangle2D bounds = function.getBoundingShape().getBounds2D();
90 | if (!bounds.contains(v.x, v.y)) {
91 | return false;
92 | }
93 |
94 | double minDist = function.getMimimumDistance(nearest);
95 | return (nearest.subtract(v).lengthSquared() > (minDist * minDist));
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/org/delaunay/algorithm/HilbertTableIndex.java:
--------------------------------------------------------------------------------
1 | package org.delaunay.algorithm;
2 |
3 | import java.awt.Point;
4 |
5 |
6 | public class HilbertTableIndex {
7 | /**
8 | * @see http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
9 | *
10 | *
11 | * Coding system applied to the four orientations of the unit shape:
12 | *
13 | *
14 | * square: 0 1 2 3
15 | *
16 | * 1--2 1--0 3--2 3 0
17 | * index: | | | | | |
18 | * 0 3 2--3 0--1 2--1
19 | *
20 | * 0--0 1--3 3--2 2 1
21 | * sub-sq: | | | | | |
22 | * 2 1 1--0 0--2 3--3
23 | *
24 | * char: A C D U
25 | *
26 | * A--A C--U U--D D C
27 | * sub-sq: | | | | | |
28 | * D C C--A A--D U--U
29 | *
30 | *
31 | */
32 |
33 | private static final int[][] squares = new int[4][];
34 | private static final int[][] indexes = new int[4][];
35 | private static final int[][] reverse = new int[4][];
36 | private static final int[][] revsqrs = new int[4][];
37 | static{
38 | // using i = xb<<1 | yb
39 | squares[0] = new int[]{2,0,1,0};
40 | squares[1] = new int[]{1,1,0,3};
41 | squares[2] = new int[]{0,3,2,2};
42 | squares[3] = new int[]{3,2,3,1};
43 |
44 | indexes[0] = new int[]{0,1,3,2};
45 | indexes[1] = new int[]{2,1,3,0};
46 | indexes[2] = new int[]{0,3,1,2};
47 | indexes[3] = new int[]{2,3,1,0};
48 |
49 | reverse[0] = new int[]{0,1,3,2};
50 | reverse[1] = new int[]{3,1,0,2};
51 | reverse[2] = new int[]{0,2,3,1};
52 | reverse[3] = new int[]{3,2,0,1};
53 |
54 | revsqrs[0] = new int[]{2,0,0,1};
55 | revsqrs[1] = new int[]{3,1,1,0};
56 | revsqrs[2] = new int[]{0,2,2,3};
57 | revsqrs[3] = new int[]{1,3,3,2};
58 | }
59 |
60 | private final int order;
61 | private final int startsquare;
62 |
63 | public HilbertTableIndex(final int order) {
64 | this(order,0);
65 | }
66 |
67 | public HilbertTableIndex(final int order, final int startsquare) {
68 | if(order <= 0) throw new IllegalArgumentException("order must be > 0");
69 | this.order = order;
70 | this.startsquare = startsquare;
71 | }
72 |
73 | public int getIndex(Point p) {
74 | final int x = p.x;
75 | final int y = p.y;
76 | if (x < 0 || x >= 1 << order || y < 0 || y >= 1 << order) return -1;
77 |
78 | int index = 0;
79 | int sq = startsquare;
80 | int o = order - 1;
81 | while (o >= 0) {
82 | int i = ((((x >> o) & 1) << 1) | ((y >> o) & 1));
83 | index = (index << 2) | indexes[sq][i];
84 | sq = squares[sq][i];
85 | o--;
86 | }
87 |
88 | return index;
89 | }
90 |
91 | public Point getPoint(int index) {
92 | if (index < 0 || index > (1 << (2*order))) return null;
93 |
94 | int x = 0;
95 | int y = 0;
96 | int sq = startsquare;
97 | int o = order - 1;
98 | while (o >= 0) {
99 | int i = (index >> (o * 2) & 3);
100 | x = x << 1 | (reverse[sq][i] >> 1 & 1);
101 | y = y << 1 | (reverse[sq][i] & 1);
102 | sq = revsqrs[sq][i];
103 | o--;
104 | }
105 | return new Point(x, y);
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/org/delaunay/dtfe/ColorScales.java:
--------------------------------------------------------------------------------
1 | package org.delaunay.dtfe;
2 |
3 | import java.awt.Color;
4 | import java.util.List;
5 |
6 | import com.google.common.collect.Lists;
7 |
8 | public class ColorScales {
9 |
10 | public static final ColorScale MODIFIED_RAINBOW = new ColorScale()
11 | .stopAlpha(Color.HSBtoRGB(0.0f, 0.6f, 1.0f), 0x00, 0.1)
12 | .stopAlpha(Color.HSBtoRGB(1.0f, 0.8f, 1.0f), 0x20, 0.4)
13 | .stopAlpha(Color.HSBtoRGB(0.8f, 0.9f, 1.0f), 0x40, 0.5)
14 | .stopAlpha(Color.HSBtoRGB(0.6f, 1.0f, 1.0f), 0x60, 0.6)
15 | .stopAlpha(Color.HSBtoRGB(0.4f, 1.0f, 1.0f), 0x80, 0.7)
16 | .stopAlpha(Color.HSBtoRGB(0.2f, 1.0f, 1.0f), 0xA0, 0.8)
17 | .stopAlpha(0xFF0000, 0xB0, 0.801)
18 | .stopAlpha(0xFF00FF, 0xB0, 1.0);
19 |
20 | public static final ColorScale LINEAR_RAINBOW = new ColorScale()
21 | .stopAlpha(Color.HSBtoRGB(0.0f, 1.0f, 1.0f), 0x00, 0.0)
22 | .stopAlpha(Color.HSBtoRGB(1.0f, 1.0f, 1.0f), 0x80, 0.2)
23 | .stopAlpha(Color.HSBtoRGB(0.8f, 1.0f, 1.0f), 0xFF, 0.4)
24 | .stopAlpha(Color.HSBtoRGB(0.6f, 1.0f, 1.0f), 0xFF, 0.6)
25 | .stopAlpha(Color.HSBtoRGB(0.4f, 1.0f, 1.0f), 0xFF, 0.8)
26 | .stopAlpha(Color.HSBtoRGB(0.2f, 1.0f, 1.0f), 0xFF, 1.0);
27 |
28 | public static final ColorScale LINEAR_RAINBOW_NO_ALPHA = new ColorScale()
29 | .stopAlpha(Color.HSBtoRGB(0.0f, 1.0f, 1.0f), 0xFF, 0.0)
30 | .stopAlpha(Color.HSBtoRGB(1.0f, 1.0f, 1.0f), 0xFF, 0.2)
31 | .stopAlpha(Color.HSBtoRGB(0.8f, 1.0f, 1.0f), 0xFF, 0.4)
32 | .stopAlpha(Color.HSBtoRGB(0.6f, 1.0f, 1.0f), 0xFF, 0.6)
33 | .stopAlpha(Color.HSBtoRGB(0.4f, 1.0f, 1.0f), 0xFF, 0.8)
34 | .stopAlpha(Color.HSBtoRGB(0.2f, 1.0f, 1.0f), 0xFF, 1.0);
35 |
36 | // http://en.wikipedia.org/wiki/Color_temperature
37 | public static final ColorScale TEMPURATURE = new ColorScale()
38 | .stopAlpha(0xFF3C00, 0x00, 0.0)
39 | .stopAlpha(0xFF7400, 0x40, 0.2)
40 | .stopAlpha(0xFFAE2E, 0x80, 0.5)
41 | .stopAlpha(0xC3C0C2, 0xE0, 0.8)
42 | .stopAlpha(0x96B1EB, 0xFF, 0.9)
43 | .stopAlpha(0x779EFF, 0xFF, 1.0);
44 |
45 | public static final ColorScale BLUE_TO_YELLOW = new ColorScale()
46 | .stopAlpha(0x0000FF, 0x00, 0.0)
47 | .stopAlpha(0x0000FF, 0x80, 0.2)
48 | .stopAlpha(0xFFFF00, 0xFF, 1.0);
49 |
50 | public static final ColorScale PURPLE_TO_GREEN = new ColorScale()
51 | .stopAlpha(0x260404, 0x00, 0.2)
52 | .stopAlpha(0x260404, 0x80, 0.4)
53 | .stopAlpha(0x6B3BAB, 0xC0, 0.6)
54 | .stopAlpha(0x4CFFB6, 0xFF, 0.9);
55 |
56 | public static final ColorScale PURPLE_TO_GREEN_LINEAR = new ColorScale()
57 | .stopAlpha(0x260404, 0x00, 0.0)
58 | .stopAlpha(0x260404, 0x80, 0.2)
59 | .stopAlpha(0x6B3BAB, 0xC0, 0.4)
60 | .stopAlpha(0x4CFFB6, 0xFF, 1.0);
61 |
62 | public static final ColorScale RED_TO_BLUE_POS_NEG = new ColorScale()
63 | .stopAlpha(0xFF0000, 0xFF, -1.0)
64 | .stopAlpha(0xFFFF00, 0x60, -0.5)
65 | .stopAlpha(0xFFFFFF, 0x00, 0.0)
66 | .stopAlpha(0x00FFFF, 0x60, 0.5)
67 | .stopAlpha(0x0000FF, 0xFF, 1.0);
68 |
69 | public static final ColorScale RED_TO_BLUE = new ColorScale()
70 | .stopAlpha(0xFF0000, 0xFF, 0.0)
71 | .stopAlpha(0xFFFF00, 0x60, 0.25)
72 | .stopAlpha(0xFFFFFF, 0x00, 0.5)
73 | .stopAlpha(0x00FFFF, 0x60, 0.75)
74 | .stopAlpha(0x0000FF, 0xFF, 1.0);
75 |
76 | public static List getDefaultColorScales() {
77 | return Lists.newArrayList(
78 | MODIFIED_RAINBOW,
79 | LINEAR_RAINBOW,
80 | TEMPURATURE,
81 | PURPLE_TO_GREEN,
82 | RED_TO_BLUE_POS_NEG,
83 | BLUE_TO_YELLOW);
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/org/delaunay/model/Triangle.java:
--------------------------------------------------------------------------------
1 | package org.delaunay.model;
2 |
3 | import java.util.LinkedHashSet;
4 | import java.util.List;
5 | import java.util.Set;
6 |
7 | import com.google.common.collect.Lists;
8 | import com.google.common.collect.Sets;
9 |
10 | public strictfp class Triangle {
11 |
12 | public final Vertex a, b, c;
13 | private final Vector circumCenter;
14 | private final double rSquared;
15 | private final LinkedHashSet vertices;
16 | private final List edges;
17 |
18 | public Triangle(Vertex v_a, Vertex v_b, Vertex v_c) {
19 | // Enforce winding rule
20 | boolean swap = v_c.orientation(v_a, v_b) > 0;
21 |
22 | this.a = v_a;
23 | this.b = swap ? v_c : v_b;
24 | this.c = swap ? v_b : v_c;
25 |
26 | this.vertices = Sets.newLinkedHashSet(Lists.newArrayList(a, b, c));
27 | this.edges = Lists.newArrayList(
28 | new Edge(a, b),
29 | new Edge(c, a),
30 | new Edge(b, c));
31 |
32 | // compute circum center
33 | // http://www.ics.uci.edu/~eppstein/junkyard/circumcenter.html
34 | double d = ((a.x - c.x) * (b.y - c.y) - (b.x - c.x) * (a.y - c.y)) * 2;
35 | double c_x = (((a.x - c.x) * (a.x + c.x) + (a.y - c.y) * (a.y + c.y)) * (b.y - c.y) -
36 | ((b.x - c.x) * (b.x + c.x) + (b.y - c.y) * (b.y + c.y)) * (a.y - c.y))
37 | / d;
38 | double c_y = (((b.x - c.x) * (b.x + c.x) + (b.y - c.y) * (b.y + c.y)) * (a.x - c.x) -
39 | ((a.x - c.x) * (a.x + c.x) + (a.y - c.y) * (a.y + c.y)) * (b.x - c.x))
40 | / d;
41 | this.circumCenter = new Vector(c_x, c_y);
42 | this.rSquared = (c.x - c_x) * (c.x - c_x) + (c.y - c_y) * (c.y - c_y);
43 | }
44 |
45 | public LinkedHashSet getVertices() {
46 | return vertices;
47 | }
48 |
49 | public Vector getCircumCenter() {
50 | return circumCenter;
51 | }
52 |
53 | public boolean isInCircum(Vector v){
54 | v = v.subtract(circumCenter);
55 | return v.lengthSquared() <= rSquared;
56 | }
57 |
58 | public Triangle nextWalk(Vector v) {
59 | if (v.orientation(b, c) > 0) {
60 | validateOpposites();
61 | return oppositeBC;
62 | } else if (v.orientation(c, a) > 0) {
63 | validateOpposites();
64 | return oppositeCA;
65 | } else if (v.orientation(a, b) > 0) {
66 | validateOpposites();
67 | return oppositeAB;
68 | }
69 | return this;
70 | }
71 |
72 | public Triangle opposite(Vertex a, Vertex b) {
73 | for (Triangle t : a.getNeighborTriangles()) {
74 | if (t != this && t.vertices.contains(b)) {
75 | return t;
76 | }
77 | }
78 | return null;
79 | }
80 |
81 | // Cache opposites for performance
82 | private Triangle oppositeAB = null;
83 | private Triangle oppositeBC = null;
84 | private Triangle oppositeCA = null;
85 | private boolean oppositesValid = false;
86 |
87 | private void validateOpposites() {
88 | if (oppositesValid) {
89 | return;
90 | }
91 | oppositeAB = opposite(a, b);
92 | oppositeBC = opposite(b, c);
93 | oppositeCA = opposite(c, a);
94 | oppositesValid = true;
95 | }
96 |
97 | public void invalidateNeighbors() {
98 | oppositesValid = false;
99 | neighborsValid = false;
100 | }
101 |
102 | private Set neighbors = null;
103 | private boolean neighborsValid = false;
104 |
105 | public Set getNeighbors() {
106 | if (neighborsValid) {
107 | return neighbors;
108 | }
109 | neighbors = Sets.newHashSet();
110 | neighbors.addAll(a.getNeighborTriangles());
111 | neighbors.addAll(b.getNeighborTriangles());
112 | neighbors.addAll(c.getNeighborTriangles());
113 | neighbors.remove(this);
114 | neighborsValid = true;
115 | return neighbors;
116 | }
117 |
118 | public Iterable getEdges() {
119 | return edges;
120 | }
121 |
122 | private Double area = null;
123 |
124 | public double getArea() {
125 | if (area == null) {
126 | area = (c.subtract(a)).cross((b.subtract(a))) / 2.0;
127 | }
128 | return area;
129 | }
130 |
131 | @Override
132 | public String toString() {
133 | return String.format("%s -> %s -> %s", a, b, c);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/org/delaunay/TriangulationDemo.java:
--------------------------------------------------------------------------------
1 | package org.delaunay;
2 |
3 | import java.awt.Color;
4 | import java.awt.image.BufferedImage;
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import javax.imageio.ImageIO;
10 |
11 | import org.delaunay.algorithm.Triangulation;
12 | import org.delaunay.algorithm.Triangulation.DebugLogger;
13 | import org.delaunay.algorithm.Triangulation.InvalidVertexException;
14 | import org.delaunay.algorithm.Triangulations;
15 | import org.delaunay.dtfe.BasicDensityModel;
16 | import org.delaunay.dtfe.ColorScales;
17 | import org.delaunay.dtfe.DensityModel;
18 | import org.delaunay.dtfe.DtfeTriangulationMap;
19 | import org.delaunay.dtfe.interpolation.InterpolationStrategies;
20 | import org.delaunay.dtfe.painters.DtfePainter;
21 | import org.delaunay.dtfe.painters.DtfePainterModel;
22 | import org.delaunay.dtfe.painters.PaintTransform;
23 | import org.delaunay.dtfe.painters.TriangulationPainter;
24 | import org.delaunay.dtfe.painters.TriangulationPainterModel;
25 | import org.delaunay.model.Vertex;
26 |
27 | /**
28 | * This class demonstrates various ways of creating triangulations and DTFEs,
29 | * and how to generate images from them.
30 | */
31 | public class TriangulationDemo {
32 | private static final int WIDTH = 800;
33 | private static final int HEIGHT = 800;
34 |
35 | public static void drawTriangulation(Triangulation t, int w, int h, String filename)
36 | throws IOException {
37 |
38 | TriangulationPainter painter = new TriangulationPainter(new TriangulationPainterModel()
39 | .setEdgeColor(new Color(0x2222AA))
40 | .setEdgeStrokeWidth(1.5f));
41 |
42 | BufferedImage img = painter.paint(t, new PaintTransform(w, h));
43 | ImageIO.write(img, "png", new File(filename));
44 | }
45 |
46 | public static void drawDtfe(DtfeTriangulationMap extends DensityModel> dtfe, int w, int h, String filename)
47 | throws IOException {
48 |
49 | DtfePainter painter = new DtfePainter(new DtfePainterModel()
50 | .setInterpolationStrategy(InterpolationStrategies.createNaturalNeighbor())
51 | .setDensityScalar(50)
52 | .setEdgeColor(new Color(0x10000000, true))
53 | .setColorScale(ColorScales.PURPLE_TO_GREEN_LINEAR));
54 |
55 | BufferedImage img = painter.paint(dtfe, new PaintTransform(w, h));
56 | ImageIO.write(img, "png", new File(filename));
57 | }
58 |
59 | public static void fourLiner() throws Exception {
60 | Triangulation t = new Triangulation();
61 | t.addAllVertices(Triangulations.randomVertices(1000, 400, 400));
62 | t.triangulate();
63 | TriangulationDemo.drawTriangulation(t, 400, 400, "triangulation.png");
64 | }
65 |
66 | public static void createTriangulationAndDtfeDemo() throws InvalidVertexException, IOException {
67 | // Generate vertices
68 |
69 | // Triangulate
70 | long start = System.nanoTime();
71 | Triangulation t = new Triangulation();
72 | t.addAllVertices(Triangulations.randomVertices(10000, WIDTH, HEIGHT));
73 | t.setDebugLogger(new DebugLogger() {
74 | public void debug(String str) {
75 | System.out.println(str);
76 | }
77 | });
78 | t.triangulate();
79 | System.out.println(String.format("Time to triangulate %,d vertices: %d msec.", 10000,
80 | TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)));
81 | System.out.println(String.format("Average hops per locate: %.2f", (float)t.getHopCount() / t.getLocateCount()));
82 |
83 | // DTFE
84 | start = System.nanoTime();
85 | DtfeTriangulationMap dtfe = new DtfeTriangulationMap();
86 | for (Vertex v : Triangulations.randomGaussian(1000, WIDTH, HEIGHT)) {
87 | dtfe.put(v.x, v.y, new BasicDensityModel());
88 | }
89 | dtfe.triangulate();
90 | System.out.println(String.format("Time to create DTFE: %d msec.",
91 | TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)));
92 |
93 | // Draw Results
94 | System.out.println("Creating images");
95 | drawTriangulation(t, WIDTH, HEIGHT, "triangulation.png");
96 | start = System.nanoTime();
97 | drawDtfe(dtfe, WIDTH, HEIGHT, "dtfe.png");
98 | System.out.println(String.format("Time to draw DTFE: %d msec.",
99 | TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)));
100 | System.out.println("Done");
101 | }
102 |
103 | public static void main(String[] args) throws Exception {
104 | //threeLiner();
105 | createTriangulationAndDtfeDemo();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/org/delaunay/dtfe/IsolineBuilder.java:
--------------------------------------------------------------------------------
1 | package org.delaunay.dtfe;
2 |
3 | import java.util.Collection;
4 | import java.util.List;
5 | import java.util.Set;
6 |
7 | import org.delaunay.dtfe.DtfeTriangulationMap.ScaleType;
8 | import org.delaunay.model.Edge;
9 | import org.delaunay.model.Triangle;
10 | import org.delaunay.model.Vector;
11 | import org.delaunay.model.Vertex;
12 |
13 | import com.google.common.collect.ArrayListMultimap;
14 | import com.google.common.collect.Iterables;
15 | import com.google.common.collect.Lists;
16 | import com.google.common.collect.Multimap;
17 | import com.google.common.collect.Sets;
18 |
19 | public class IsolineBuilder {
20 |
21 | private final DtfeTriangulationMap extends DensityModel> dtfe;
22 | private ScaleType scaleType = ScaleType.LOG;
23 |
24 | public IsolineBuilder(DtfeTriangulationMap extends DensityModel> dtfe) {
25 | this.dtfe = dtfe;
26 | }
27 |
28 | public IsolineBuilder setScaleType(ScaleType scaleType) {
29 | this.scaleType = scaleType;
30 | return this;
31 | }
32 |
33 | /**
34 | * Returns a list of list of vectors. Each list of vectors is a continuous
35 | * isoline at the specified value.
36 | */
37 | public List> getIsoLines(double value) {
38 | Set segments = Sets.newLinkedHashSet();
39 | Multimap segmentsMap = ArrayListMultimap.create();
40 |
41 | // Compute segments from triangles that pass through value
42 | for (Triangle tri : dtfe.getTriangulation().getTriangles()) {
43 | // Compare vertex density with iso value
44 | List above = Lists.newArrayList();
45 | List below = Lists.newArrayList();
46 | for (Vertex v : tri.getVertices()) {
47 | if (dtfe.getRelativeDensity(dtfe.getDensity(v), scaleType) > value) {
48 | above.add(v);
49 | } else {
50 | below.add(v);
51 | }
52 | }
53 |
54 | // All vertices above or below value
55 | if (above.size() == 3 || below.size() == 3 || above.size() + below.size() != 3) {
56 | continue;
57 | }
58 | // One vertex above value (2 below)
59 | else if (above.size() == 1) {
60 | Segment segment = new Segment(Iterables.getOnlyElement(above), below.get(0), below.get(1), value);
61 | segmentsMap.put(segment.e0, segment);
62 | segmentsMap.put(segment.e1, segment);
63 | segments.add(segment);
64 | }
65 | // One vertex below value (2 above)
66 | else if (below.size() == 1) {
67 | Segment segment = new Segment(Iterables.getOnlyElement(below), above.get(0), above.get(1), value);
68 | segmentsMap.put(segment.e0, segment);
69 | segmentsMap.put(segment.e1, segment);
70 | segments.add(segment);
71 | }
72 | }
73 |
74 | // Connect line segments into lists of vectors
75 | List> paths = Lists.newArrayList();
76 | while (!segments.isEmpty()) {
77 | // Poll any segment
78 | Segment segment = Iterables.getFirst(segments, null);
79 | segments.remove(segment);
80 |
81 | Edge startEdge = segment.e0;
82 | Edge headEdge = segment.e1;
83 | List vectors = Lists.newArrayList(segment.v0, segment.v1);
84 |
85 | while (segment != null) {
86 | // Find other segment that touches the same edge
87 | Collection segs = segmentsMap.get(headEdge);
88 | segs.remove(segment);
89 | Segment nextSegment = Iterables.getFirst(segs, null);
90 |
91 | if (nextSegment == null) {
92 | // TODO log error
93 | break;
94 | }
95 |
96 | segments.remove(nextSegment);
97 |
98 | // Connect the segment (switch direction of segment if necessary)
99 | if (headEdge.equals(nextSegment.e0)) {
100 | vectors.add(nextSegment.v1);
101 | headEdge = nextSegment.e1;
102 | } else {
103 | vectors.add(nextSegment.v0);
104 | headEdge = nextSegment.e0;
105 | }
106 | segment = nextSegment;
107 |
108 | // If we reach the beginning, this path is complete
109 | if (headEdge.equals(startEdge)) {
110 | paths.add(vectors);
111 | break;
112 | }
113 | }
114 | }
115 | return paths;
116 | }
117 |
118 | private class Segment {
119 | final Vector v0;
120 | final Edge e0;
121 | final Vector v1;
122 | final Edge e1;
123 |
124 | private Segment(Vertex vCommon, Vertex v0, Vertex v1, double value) {
125 | // Compute the location of the segment endpoints based on the
126 | // density of the vertices and the iso value
127 | double dCommon = dtfe.getRelativeDensity(dtfe.getDensity(vCommon), scaleType);
128 | double d0 = dtfe.getRelativeDensity(dtfe.getDensity(v0), scaleType);
129 | double d1 = dtfe.getRelativeDensity(dtfe.getDensity(v1), scaleType);
130 | double t0 = (value - dCommon) / (d0 - dCommon);
131 | double t1 = (value - dCommon) / (d1 - dCommon);
132 |
133 | this.e0 = new Edge(vCommon, v0);
134 | this.v0 = v0.subtract(vCommon).multiply(t0).add(vCommon);
135 | this.e1 = new Edge(vCommon, v1);
136 | this.v1 = v1.subtract(vCommon).multiply(t1).add(vCommon);
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/org/delaunay/ExampleImagesDemo.java:
--------------------------------------------------------------------------------
1 | package org.delaunay;
2 |
3 | import java.awt.Color;
4 | import java.awt.Rectangle;
5 | import java.awt.image.BufferedImage;
6 | import java.io.File;
7 | import java.io.IOException;
8 | import java.util.Random;
9 | import java.util.concurrent.TimeUnit;
10 |
11 | import javax.imageio.ImageIO;
12 |
13 | import org.delaunay.algorithm.Triangulations;
14 | import org.delaunay.dtfe.BasicDensityModel;
15 | import org.delaunay.dtfe.ColorScale;
16 | import org.delaunay.dtfe.ColorScales;
17 | import org.delaunay.dtfe.DtfeTriangulationMap;
18 | import org.delaunay.dtfe.Dtfes;
19 | import org.delaunay.dtfe.Dtfes.DifferenceDensityModel;
20 | import org.delaunay.dtfe.interpolation.InterpolationStrategies;
21 | import org.delaunay.dtfe.painters.ColorScaleLegendPainter;
22 | import org.delaunay.dtfe.painters.ColorScaleLegendPainter.ColorScalePainterModel;
23 | import org.delaunay.dtfe.painters.DtfePainter;
24 | import org.delaunay.dtfe.painters.DtfePainterModel;
25 | import org.delaunay.dtfe.painters.PaintTransform;
26 | import org.delaunay.dtfe.painters.TriangulationPainter;
27 | import org.delaunay.dtfe.painters.TriangulationPainterModel;
28 | import org.delaunay.model.Vertex;
29 |
30 | /**
31 | * This class generates the images that are used in the wiki gallery on github.
32 | */
33 | @SuppressWarnings("unused")
34 | public class ExampleImagesDemo {
35 | private static final int WIDTH = 800;
36 | private static final int HEIGHT = 800;
37 |
38 | public static void createInterpolationExamples() throws Exception {
39 | // Generate random distribution of points
40 | DtfeTriangulationMap dtfe = new DtfeTriangulationMap();
41 | for (Vertex v : Triangulations.randomGaussian(1000, WIDTH, HEIGHT)) {
42 | dtfe.put(v.x, v.y, new BasicDensityModel());
43 | }
44 | dtfe.triangulate();
45 |
46 | // Make sure we have dirs
47 | new File("examples").mkdirs();
48 |
49 | // Draw overall triangulation
50 | long start = System.nanoTime();
51 | BufferedImage img = new TriangulationPainter(new TriangulationPainterModel()
52 | .setEdgeColor(Color.BLACK)
53 | .setEdgeStrokeWidth(1.5f)).paint(
54 | dtfe.getTriangulation(),
55 | new PaintTransform(WIDTH, HEIGHT));
56 | System.out.println(String.format("examples/dtfe_triangulation_full.png: %d msec.", TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)));
57 | ImageIO.write(img, "png", new File("examples/dtfe_triangulation_full.png"));
58 |
59 | // Draw individual interpolations
60 | DtfePainterModel model = new DtfePainterModel()
61 | .setDensityScalar(35)
62 | .setColorScale(ColorScales.PURPLE_TO_GREEN_LINEAR);
63 | model.setInterpolationStrategy(InterpolationStrategies.createMean());
64 | createSampleImages(dtfe, model, "mean");
65 | model.setInterpolationStrategy(InterpolationStrategies.createNearestNeighbor());
66 | createSampleImages(dtfe, model, "near_neighbor");
67 | model.setInterpolationStrategy(InterpolationStrategies.createBarycentricLinear());
68 | createSampleImages(dtfe, model, "linear");
69 | model.setInterpolationStrategy(InterpolationStrategies.createNaturalNeighbor());
70 | createSampleImages(dtfe, model, "natural_neighbor");
71 | }
72 |
73 | private static void createSampleImages(DtfeTriangulationMap extends BasicDensityModel> dtfe,
74 | DtfePainterModel model, String name) throws IOException {
75 | Rectangle fullRect = new Rectangle(0, 0, WIDTH, HEIGHT);
76 | Rectangle zoomRect = new Rectangle(WIDTH / 2 + 50, HEIGHT / 2 - 100, 200, 200);
77 |
78 | long start = System.nanoTime();
79 | String file0 = "examples/dtfe_interp_" + name + "_full.png";
80 | ImageIO.write(new DtfePainter(model).paint(dtfe, new PaintTransform(WIDTH, HEIGHT, fullRect)),
81 | "png", new File(file0));
82 | System.out.println(String.format("%s: %d msec.", file0, TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)));
83 |
84 | start = System.nanoTime();
85 | String file1 = "examples/dtfe_interp_" + name + "_zoom.png";
86 | ImageIO.write(new DtfePainter(model).paint(dtfe, new PaintTransform(WIDTH, HEIGHT, zoomRect)),
87 | "png", new File(file1));
88 | System.out.println(String.format("%s: %d msec.", file1, TimeUnit.MILLISECONDS.convert(System.nanoTime() - start, TimeUnit.NANOSECONDS)));
89 | }
90 |
91 | private static void createPosNegExample() throws Exception {
92 | // Generate random distribution of points with positive and negative weights
93 | DtfeTriangulationMap dtfe = new DtfeTriangulationMap();
94 | Random random = new Random(System.currentTimeMillis());
95 | for (Vertex v : Triangulations.randomGaussian(1000, WIDTH, HEIGHT)) {
96 | dtfe.put(v.x, v.y, new BasicDensityModel(random.nextDouble() * (random.nextBoolean() ? 1 : -1)));
97 | }
98 | dtfe.triangulate();
99 |
100 | // Paint
101 | DtfePainterModel model = new DtfePainterModel()
102 | .setDensityScalar(35)
103 | .setColorScale(ColorScales.RED_TO_BLUE_POS_NEG);
104 | model.setInterpolationStrategy(InterpolationStrategies.createBarycentricLinear());
105 | createSampleImages(dtfe, model, "posneg");
106 | }
107 |
108 | private static void createDiffExample() throws Exception {
109 | // Generate random distribution of points
110 | DtfeTriangulationMap dtfeA = new DtfeTriangulationMap();
111 | DtfeTriangulationMap dtfeB = new DtfeTriangulationMap();
112 | Random random = new Random(System.currentTimeMillis());
113 | for (Vertex v : Triangulations.randomGaussian(1000, WIDTH, HEIGHT)) {
114 | dtfeA.put(v.x - WIDTH / 32, v.y - HEIGHT / 32, new BasicDensityModel(0.5 + random.nextDouble()));
115 | }
116 | for (Vertex v : Triangulations.randomGaussian(1000, WIDTH, HEIGHT)) {
117 | dtfeB.put(v.x + WIDTH / 32, v.y + HEIGHT / 32, new BasicDensityModel(0.5 + random.nextDouble()));
118 | }
119 | dtfeA.triangulate();
120 | dtfeB.triangulate();
121 |
122 | // Paint individual dtfes
123 | DtfePainterModel modelAB = new DtfePainterModel()
124 | .setDensityScalar(25)
125 | .setColorScale(ColorScales.PURPLE_TO_GREEN_LINEAR)
126 | .setInterpolationStrategy(InterpolationStrategies.createBarycentricLinear());
127 | createSampleImages(dtfeA, modelAB, "diff_a");
128 | createSampleImages(dtfeB, modelAB, "diff_b");
129 |
130 | // Create difference
131 | DtfeTriangulationMap> diff = Dtfes.difference(
132 | dtfeA, dtfeB, InterpolationStrategies.createBarycentricLinear());
133 |
134 | // Paint diff dtfe
135 | createSampleImages(diff, new DtfePainterModel()
136 | .setDensityScalar(25)
137 | .setColorScale(ColorScales.RED_TO_BLUE_POS_NEG)
138 | .setInterpolationStrategy(InterpolationStrategies.createBarycentricLinear()), "diff");
139 | }
140 |
141 | private static void createScaleExamples() throws Exception {
142 | ColorScaleLegendPainter painter = new ColorScaleLegendPainter(new ColorScalePainterModel());
143 | paintScale(painter, ColorScales.LINEAR_RAINBOW, "rainbow");
144 | paintScale(painter, ColorScales.PURPLE_TO_GREEN_LINEAR, "purple_green");
145 | paintScale(painter, ColorScales.TEMPURATURE, "tempurature");
146 | paintScale(painter, ColorScales.RED_TO_BLUE_POS_NEG, "red_blue_pos_neg");
147 | paintScale(painter, ColorScales.BLUE_TO_YELLOW, "blue_yellow");
148 | }
149 |
150 | private static void paintScale(
151 | ColorScaleLegendPainter painter,
152 | ColorScale scale,
153 | String name) throws IOException {
154 | String path = "examples/colorscale_" + name + ".png";
155 | ImageIO.write(painter.createLegendImage(scale), "png", new File(path));
156 | }
157 |
158 | public static void main(String[] args) throws Exception {
159 | //createInterpolationExamples();
160 | //createPosNegExample();
161 | //createDiffExample();
162 | createScaleExamples();
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/org/delaunay/algorithm/Triangulation.java:
--------------------------------------------------------------------------------
1 | package org.delaunay.algorithm;
2 |
3 | import java.awt.geom.Rectangle2D;
4 | import java.util.Collection;
5 | import java.util.Collections;
6 | import java.util.Comparator;
7 | import java.util.LinkedHashSet;
8 | import java.util.List;
9 | import java.util.Set;
10 |
11 | import org.delaunay.model.Edge;
12 | import org.delaunay.model.Triangle;
13 | import org.delaunay.model.Vector;
14 | import org.delaunay.model.Vectors;
15 | import org.delaunay.model.Vertex;
16 |
17 | import com.google.common.base.Function;
18 | import com.google.common.base.Predicate;
19 | import com.google.common.collect.Iterables;
20 | import com.google.common.collect.Lists;
21 | import com.google.common.collect.Sets;
22 |
23 | /**
24 | * A fast Delaunay Triangulation implementation.
25 | */
26 | @SuppressWarnings("serial")
27 | public strictfp class Triangulation {
28 |
29 | public static class NonDelaunayException extends RuntimeException {
30 | }
31 |
32 | public static class InvalidVertexException extends Exception {
33 | }
34 |
35 | public static interface DebugLogger{
36 | public void debug(String str);
37 | }
38 |
39 | public static enum VertexExceptionStrategy{
40 | THROW_EXCEPTION,
41 | CATCH_AND_DROP_VERTEX,
42 | ;
43 | }
44 |
45 | private Vertex[] superVerts = new Vertex[]{};
46 | private LinkedHashSet triangles = Sets.newLinkedHashSet();
47 | private LinkedHashSet inputVertices = Sets.newLinkedHashSet();
48 | private LinkedHashSet vertices = Sets.newLinkedHashSet();
49 | private Triangle lastLocatedTriangle = null;
50 | private int hopCount = 0;
51 | private int locateCount = 0;
52 | private boolean keepSuperTriangle = false;
53 | private VertexExceptionStrategy vertexExceptionStrategy = VertexExceptionStrategy.THROW_EXCEPTION;
54 |
55 | /*
56 | * The hilbert order determines the granularity of the hilbert curve. For
57 | * example, a value of 16 produces a square with with length and width 2^16,
58 | * resulting in 2^16 * 2^16 = 2^32 cells. This is typically good enough for
59 | * a triangulation up to 4 Billion vertices. Running time for coordinate
60 | * conversion would be O(16).
61 | */
62 | private int hilbertOrder = 16;
63 |
64 | /*
65 | * Determines the scale of the super triangle. Increase this number if you
66 | * need to vertex locates from farther out from the bounding box of the
67 | * vertices.
68 | */
69 | private double superTriangleScale = 2.0;
70 |
71 | private DebugLogger log = new DebugLogger() {
72 | public void debug(String str) {
73 | // null implementation
74 | }
75 | };
76 |
77 | public int getHopCount() {
78 | return hopCount;
79 | }
80 |
81 | public int getLocateCount() {
82 | return locateCount;
83 | }
84 |
85 | public void setDebugLogger(DebugLogger log) {
86 | this.log = log;
87 | }
88 |
89 | public Vertex addVertex(double x, double y) {
90 | Vertex vertex = new Vertex(x, y);
91 | inputVertices.add(vertex);
92 | return vertex;
93 | }
94 |
95 | public void addVertex(Vertex v) {
96 | inputVertices.add(v);
97 | }
98 |
99 | public void addAllVertices(Iterable vs) {
100 | Iterables.addAll(inputVertices, vs);
101 | }
102 |
103 | public LinkedHashSet getInputVertices() {
104 | return inputVertices;
105 | }
106 |
107 | public LinkedHashSet getVertices() {
108 | return vertices;
109 | }
110 |
111 | public List getVerticesInBounds(final Rectangle2D rect) {
112 | return Lists.newArrayList(Iterables.filter(getVertices(), new Predicate() {
113 | public boolean apply(Vertex v) {
114 | return rect.contains(v.toPoint());
115 | }
116 | }));
117 | }
118 |
119 | public LinkedHashSet getTriangles() {
120 | return triangles;
121 | }
122 |
123 | /**
124 | * If set to true, the supertriangle will not be removed at the end of the
125 | * triangulation method. This allows points outside the convex hull of
126 | * vertices to be located.
127 | */
128 | public void setKeepSuperTriangle(boolean keepSuperTriangle) {
129 | this.keepSuperTriangle = keepSuperTriangle;
130 | }
131 |
132 | public Vertex locateNearestVertex(Vector v) {
133 | Triangle located = locate(v);
134 | if (located == null) {
135 | return null;
136 | }
137 |
138 | Vertex bestVertex = null;
139 | double dist = Double.MAX_VALUE;
140 |
141 | for (Triangle tri : getCircumcircleTriangles(v, located)) {
142 | for (Vertex vert : tri.getVertices()) {
143 | double d = vert.subtract(v).lengthSquared();
144 | if (d < dist) {
145 | bestVertex = vert;
146 | dist = d;
147 | }
148 | }
149 | }
150 | return bestVertex;
151 | }
152 |
153 | public Set getVerticesInRadius(Vertex v, double radius) {
154 | Set checked = Sets.newHashSet(v);
155 | Set inRadius = Sets.newHashSet(v);
156 | Set toCheck = Sets.newHashSet(v.getNeighborVertices());
157 |
158 | while (toCheck.size() > 0) {
159 | Vertex check = Iterables.getFirst(toCheck, null);
160 | toCheck.remove(check);
161 | checked.add(check);
162 |
163 | if (v.subtract(check).length() < radius) {
164 | inRadius.add(check);
165 | toCheck.addAll(check.getNeighborVertices());
166 | toCheck.removeAll(checked);
167 | }
168 | }
169 |
170 | return inRadius;
171 | }
172 |
173 | /**
174 | * Creates a Delaunay Triangulation of the {@link Vertex}s.
175 | *
176 | * This implementation uses a simple iterative approach, but with some
177 | * pre-processing to make the real-world performance fast.
178 | *
179 | * First, the vertices are sorted using a Hilbert Space-Filling curve. Since
180 | * our locate method walks the triangulation, linearizing the points with a
181 | * space-filling curve gives us some pretty good locality when adding each
182 | * vertex, thus greatly reducing the number of hops required to locate the
183 | * vertex. The sort is O(n log n), but is fast since hilbert indices are
184 | * computed in O(h) (where h is a small constant), and results in a
185 | * triangulation asymptotic running time of O(n) for non-diabolical cases.
186 | * For more info, see: {@link http://en.wikipedia.org/wiki/Hilbert_curve}
187 | *
188 | * For each vertex, we walk to the enclosing triangle. We create a cavity
189 | * from that triangle and all neighboring triangles for which the vertex is
190 | * in their circumcircle.
191 | *
192 | * Then, we create new triangles between the edges of the cavity and the
193 | * vertex.
194 | *
195 | * We guarantee that a triangle will be located for each vertex by first
196 | * creating a "super triangle" that is at least twice as large as the bounds
197 | * of the vertices. If this triangulation will be used for point location,
198 | * you will want to call {@link #setKeepSuperTriangle(true)} so that points
199 | * outside the convex hull of vertices may also be located.
200 | *
201 | * Basic incremental triangulation method inspired by Paul Bourke's notes
202 | * and psuedocode. See: {@link http://paulbourke.net/papers/triangulate/}
203 | *
204 | * @throws InvalidVertexException
205 | * if any two vertices have the same location or if any three
206 | * points are co-linear.
207 | */
208 | public void triangulate() throws InvalidVertexException {
209 | /*
210 | * Reset triangulation state
211 | */
212 | resetTriangulation();
213 |
214 | if (Iterables.isEmpty(inputVertices)) {
215 | return;
216 | }
217 |
218 | /*
219 | * Determine the supertriangle.
220 | */
221 | createSuperTriangle(inputVertices);
222 |
223 | /*
224 | * Sort vertices using hilbert curve to linearize triangulation
225 | * performance.
226 | */
227 | log.debug("Linearizing with Hilbert Space-filling Curve");
228 | List sortedVertices = getHilbertSortedVertices(inputVertices);
229 |
230 | /*
231 | * Add vertices one at a time, updating the triangulation as we go.
232 | */
233 | log.debug("Building Triangulation");
234 | for (Vertex vertex : sortedVertices) {
235 | try {
236 | addVertexToTriangulation(vertex);
237 | } catch (InvalidVertexException e) {
238 | if (vertexExceptionStrategy == VertexExceptionStrategy.THROW_EXCEPTION) {
239 | throw e;
240 | } else {
241 | // ignore
242 | }
243 | }
244 | }
245 |
246 | /*
247 | * Cleanup
248 | */
249 | clearLocator();
250 | if (!keepSuperTriangle) {
251 | removeSuperTriangle();
252 | }
253 |
254 | log.debug("Triangulation Complete");
255 | }
256 |
257 | public void clear() {
258 | resetTriangulation();
259 | inputVertices = Sets.newLinkedHashSet();
260 | }
261 |
262 | private void resetTriangulation() {
263 | triangles = Sets.newLinkedHashSet();
264 | vertices = Sets.newLinkedHashSet();
265 | clearLocator();
266 | }
267 |
268 | private List getHilbertSortedVertices(Iterable extends Vertex> verts) {
269 | Rectangle2D bbox = Vectors.boundingBox(Lists.newArrayList(superVerts));
270 | ScaledHilbertIndex hilbert = new ScaledHilbertIndex(hilbertOrder, bbox);
271 | for (Vertex v : verts) {
272 | v.setHilbertIndex(hilbert.toIndex(v.x, v.y));
273 | }
274 | List sortedVertices = Lists.newArrayList(verts);
275 | Collections.sort(sortedVertices, new Comparator() {
276 | public int compare(Vertex v1, Vertex v2) {
277 | return v1.getHilbertIndex().compareTo(v2.getHilbertIndex());
278 | }
279 | });
280 | return sortedVertices;
281 | }
282 |
283 | public void addVertexToTriangulation(Vertex vertex) throws InvalidVertexException {
284 | Collection toRemove = null, toAdd = null;
285 |
286 | try {
287 | /*
288 | * Get the set of triangles for which the vertex lies in its
289 | * circumcircle.
290 | */
291 | toRemove = getCircumcircleTriangles(vertex);
292 | } catch (NonDelaunayException e) {
293 | /*
294 | * Unfortunately, we cannot recover from this state since the
295 | * triangulation is already non-delaunay. It was probably caused
296 | * by overlapping vertices, so we throw an invalid vertex
297 | * exception.
298 | */
299 | throw new InvalidVertexException();
300 | } catch (InvalidVertexException e) {
301 | log.debug(String.format("Dropping vertex %s because it outside the triangulation!\nMaybe something went wrong when computing the super triangle?", vertex));
302 | return;
303 | }
304 |
305 | /*
306 | * Compute the set of edges that represent the convex hull of the
307 | * cavity left by removing the triangles.
308 | */
309 | List edgeSet = getEdgeSet(toRemove);
310 |
311 | /*
312 | * Remove the triangles.
313 | */
314 | removeTriangles(toRemove);
315 |
316 | try {
317 | /*
318 | * Create and add triangles created from the cavity convex hull
319 | * edges and the vertex.
320 | */
321 | toAdd = createTriangles(edgeSet, vertex);
322 | addTriangles(toAdd);
323 | vertices.add(vertex);
324 | } catch (NonDelaunayException e) {
325 | log.debug(String.format("Dropping vertex %s because it causes degeneracy.\nYou may need to use exact math on this vertex.", vertex));
326 | removeTriangles(toAdd);
327 | addTriangles(toRemove);
328 | }
329 | }
330 |
331 | public void createSuperTriangle(Iterable extends Vertex> verts) {
332 | createSuperTriangle(Vectors.boundingBox(verts));
333 | }
334 |
335 | public void createSuperTriangle(Rectangle2D rect) {
336 | double dmax = Math.max(rect.getWidth(), rect.getHeight());
337 | double xmid = rect.getCenterX();
338 | double ymid = rect.getCenterY();
339 |
340 | superVerts = new Vertex[] {
341 | new Vertex(xmid - dmax * superTriangleScale, ymid - dmax),
342 | new Vertex(xmid, ymid + dmax * superTriangleScale),
343 | new Vertex(xmid + dmax * superTriangleScale, ymid - dmax)
344 | };
345 |
346 | triangles = Sets.newLinkedHashSet();
347 | triangles.add(new Triangle(superVerts[0], superVerts[1], superVerts[2]));
348 | }
349 |
350 | public void removeSuperTriangle() {
351 | Set touching = Sets.newHashSet();
352 | for (Vertex v : superVerts) {
353 | touching.addAll(v.getNeighborTriangles());
354 | }
355 | removeTriangles(touching);
356 | superVerts = new Vertex[] {};
357 | }
358 |
359 | public boolean touchesSuperVertex(Triangle tri) {
360 | for (Vertex v : superVerts) {
361 | if (tri.getVertices().contains(v)) {
362 | return true;
363 | }
364 | }
365 | return false;
366 | }
367 |
368 | public boolean neighborsSuperVertex(Vertex vert) {
369 | for (Triangle tri : vert.getNeighborTriangles()) {
370 | if (touchesSuperVertex(tri)) {
371 | return true;
372 | }
373 | }
374 | if (Sets.newHashSet(superVerts).contains(vert)) {
375 | return true;
376 | }
377 | return false;
378 | }
379 |
380 | private void clearLocator() {
381 | lastLocatedTriangle = null;
382 | }
383 |
384 | public Collection getCircumcircleTriangles(Vector vertex) throws InvalidVertexException, NonDelaunayException {
385 | Triangle t = locate(vertex);
386 | if (t == null) {
387 | throw new InvalidVertexException();
388 | }
389 | return getCircumcircleTriangles(vertex, t);
390 | }
391 |
392 | private Collection getCircumcircleTriangles(Vector vertex, Triangle t) {
393 | Set checked = Sets.newHashSet(t);
394 | Set inCircum = Sets.newHashSet(t);
395 |
396 | // Initialize "to check" set with neighbors
397 | Set toCheck = Sets.newHashSet(t.getNeighbors());
398 |
399 | // For first triangle in "to check" set, check if
400 | // the vertex is in its circum circle.
401 | while (toCheck.size() > 0) {
402 | t = Iterables.getFirst(toCheck, null);
403 | toCheck.remove(t);
404 |
405 | if (t.isInCircum(vertex)) {
406 | inCircum.add(t);
407 | // If it is, add *its* neighbors to the "to check" set.
408 | toCheck.addAll(t.getNeighbors());
409 | toCheck.removeAll(checked);
410 | }
411 | checked.add(t);
412 | }
413 |
414 | return inCircum;
415 | }
416 |
417 | /*
418 | * Walks the triangulation toward the vertex. Returns the triangle in which
419 | * the vertex resides. If the vertex is outside the current triangulation,
420 | * nil is returned.
421 | *
422 | * It is possible that if the triangulation breaks due to floating point
423 | * errors, it will cause errors during locate. In this case, we throw a
424 | * NonDelaunayException.
425 | *
426 | * If the vertices are near each other, such as when iterating over a
427 | * hilbert linearization or running a scanline of locations, this should be
428 | * pretty fast.
429 | */
430 | public Triangle locate(Vector v) throws NonDelaunayException {
431 | locateCount += 1;
432 | Triangle t = lastLocatedTriangle == null ? Iterables.getFirst(triangles, null) : lastLocatedTriangle;
433 | if (t == null) {
434 | return null;
435 | }
436 | boolean done = false;
437 |
438 | Set seen = Sets. newHashSet();
439 | while (!done) {
440 | hopCount += 1;
441 | lastLocatedTriangle = t;
442 | if (seen.contains(t)) {
443 | throw new NonDelaunayException();
444 | }
445 | seen.add(t);
446 | Triangle tNext = t.nextWalk(v);
447 | if (tNext == null) {
448 | return null;
449 | }
450 | done = (tNext == t);
451 | t = tNext;
452 | }
453 |
454 | /*
455 | * During triangulation the located triangle is immediately removed.
456 | * But, it can be useful to store this if we are locating points in the
457 | * triangulation after it's constructed.
458 | */
459 | lastLocatedTriangle = t;
460 | return t;
461 | }
462 |
463 | public List getEdgeSet(Collection tris) {
464 | List edges = Lists.newArrayList();
465 | for (Triangle t : tris) {
466 | for (Edge e : t.getEdges()) {
467 | if (edges.contains(e)) {
468 | edges.remove(e);
469 | } else {
470 | edges.add(e);
471 | }
472 | }
473 | }
474 | return edges;
475 | }
476 |
477 | public List createTriangles(Iterable edgeSet, final Vertex vertex) {
478 | return Lists.newArrayList(Iterables.transform(edgeSet, new Function() {
479 | public Triangle apply(Edge e) {
480 | return new Triangle(vertex, e.a, e.b);
481 | }
482 | }));
483 | }
484 |
485 | private void addTriangles(Iterable tris) throws NonDelaunayException {
486 | for (Triangle t : tris) {
487 | for (Vertex v : t.getVertices()) {
488 | v.addTriangle(t);
489 | }
490 | triangles.add(t);
491 | }
492 | lastLocatedTriangle = Iterables.getFirst(tris, null);
493 |
494 | // uncomment to debug robustness issues at the cost of performance
495 | // triangles.each { |t| raise NonDelaunayException unless t.delaunay? }
496 | }
497 |
498 | private void removeTriangles(Iterable tris) {
499 | for (Triangle t : tris) {
500 | for (Vertex v : t.getVertices()) {
501 | v.removeTriangle(t);
502 | }
503 | triangles.remove(t);
504 | }
505 | lastLocatedTriangle = null;
506 | }
507 | }
508 |
--------------------------------------------------------------------------------