/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Voronoi.java * Creation date: (1/31/01 9:43:20 AM) * By: Luke Evans */ package org.openquark.gems.client.internal.effects; import java.awt.Color; import java.awt.Graphics; import java.awt.Rectangle; /** * This class implements a Voronoi diagram suitable for use in creating a 'broken' effect * which we use on Gems to indicate that they are not complete or valid in some way. * <P> * The code in this class (in particular the sweep algorithm) was ported from Steven Fortune's C code on netlib at * <a href='http://www.netlib.org/voronoi/sweep2'>http://www.netlib.org/voronoi/sweep2</a>. * It is used here by permission of Steven Fortune, who says that "the code is in the public domain at this point". * <p> * Creation date: (1/31/01 9:43:20 AM) * <p> * @author Luke Evans */ public final class Voronoi { private Site sites[]; private EventHeap heap; private Site bottomSite; private float deltaX; private float deltaY; private boolean doShowHeapCircles; private boolean doShowTriangles; private boolean doShowVoronoi; private EdgeList edgeList; private float maxX; private float maxY; private float minX; private float minY; private Site newIntStar; /* Some variables for sweeplinealgo... */ private Site newSite; private HalfEdge resultEdgeList; private int siteIdx; private float xMax; private float xMin; private float xOffs; private float xScale = 1.0F; private float yMax; private float yMin; private float yOffs; private float yScale = 1.0F; /** * The Edge class describes a single line (edge) * Creation date: (1/31/01 10:38:33 AM) * @author Luke Evans */ public static class Edge { private final float a, b, c; Site ep1; // mutable (ugh) Site ep2; // mutable (ugh) private final Site reg1; private final Site reg2; /** * Constuct an Edge from all parameters * Creation date: (1/31/01 10:45:03 AM) */ Edge(Site myreg1, Site myreg2, Site myep1, Site myep2, float mya, float myb, float myc) { reg1 = myreg1; reg2 = myreg2; ep1 = myep1; ep2 = myep2; a = mya; b = myb; c = myc; } } /** * Class for storing the currently active halfedge associated with the sweeping * Creation date: (1/31/01 10:43:06 AM) * @author Luke Evans */ static class EdgeList { private final HalfEdge hashTable[]; private final HalfEdge leftEnd; private final HalfEdge rightEnd; private final float minX; private final float deltaX; /** * Create an EdgeList from the number of sites, minimum X coord and the X delta * Creation date: (1/31/01 10:47:16 AM) * @param siteNum float number of sites */ EdgeList(int siteNum, float minX, float deltaX) { this.minX = minX; this.deltaX = deltaX; hashTable = new HalfEdge[siteNum * 2]; leftEnd = createEdge(null, true); rightEnd = createEdge(null, true); leftEnd.leftEdge = null; leftEnd.rightEdge = rightEnd; rightEnd.leftEdge = leftEnd; rightEnd.rightEdge = null; hashTable[0] = leftEnd; hashTable[hashTable.length - 1] = rightEnd; } /** * Create a new half edge in the list * Creation date: (1/31/01 10:50:30 AM) * @param e Edge the edge * @return HalfEdge */ HalfEdge createEdge(Edge e, boolean isLeftToRight) { HalfEdge newEdge = new HalfEdge(); newEdge.edge = e; newEdge.isLeftToRight = isLeftToRight; newEdge.hashNext = null; newEdge.vertex = null; return newEdge; } /** * Insert a new edge into the linked list after lb * Creation date: (1/31/01 12:58:24 PM) * @param lb HalfEdge the edge to insert after * @param newEdge HalfEdge the edge to insert */ void insertEdge(HalfEdge lb, HalfEdge newEdge) { newEdge.leftEdge = lb; newEdge.rightEdge = lb.rightEdge; lb.rightEdge.leftEdge = newEdge; lb.rightEdge = newEdge; } /** * Get entry from hash table, pruning any deleted nodes * Creation date: (1/31/01 10:55:16 AM) * @param idx int the index to look up * @return HalfEdge the half edge at the given index */ HalfEdge getHashEdge(int idx) { HalfEdge e; e = hashTable[idx]; if (e == null) return null; if ((e.leftEdge == null) && (e.rightEdge == null)) { // Deleted ? hashTable[idx] = null; return null; } return e; } /** * Find the left bounding edge * Creation date: (1/31/01 10:56:35 AM) * @param p Site starting point (to start looking for an edge) * @return HalfEdge the half edge we found */ HalfEdge getLeftBoundEdge(Site p) { int i; HalfEdge e; int idx; /* Use hash table to get close to desired halfedge */ idx = (int) ((p.x - minX) / deltaX * hashTable.length); if (idx < 0) idx = 0; if (idx >= hashTable.length) idx = hashTable.length - 1; e = getHashEdge(idx); if (e == null) { for (i = 1; true; i++) { e = getHashEdge(idx - i); if (e != null) break; e = getHashEdge(idx + i); if (e != null) break; } } // Now search linear list of halfedges for the correct one if ((e == leftEnd) || ((e != rightEnd) && (right_of(e, p)))) { do { e = e.rightEdge; } while ((e != rightEnd) && (right_of(e, p))); e = e.leftEdge; } else { do { e = e.leftEdge; } while ((e != leftEnd) && (!right_of(e, p))); } /* Update hash table and reference counts */ if ((idx > 0) && (idx < hashTable.length - 1)) hashTable[idx] = e; return e; } /** * Delete a halfedge * Creation date: (1/31/01 10:52:55 AM) * @param edge HalfEdge the edge to remove */ void deleteEdge(HalfEdge edge) { edge.leftEdge.rightEdge = edge.rightEdge; edge.rightEdge.leftEdge = edge.leftEdge; edge.leftEdge = null; edge.rightEdge = null; } /** * Return the right half edge * Creation date: (1/31/01 1:49:48 PM) * @param e HalfEdge the edge * @return HalfEdge the right half edge */ HalfEdge rightEdge(HalfEdge e) { return e.rightEdge; } /** * Return the left half edge * Creation date: (1/31/01 1:49:48 PM) * @param e HalfEdge the edge * @return HalfEdge the left half edge */ HalfEdge leftEdge(HalfEdge e) { return e.leftEdge; } /** * Determine if a Site is to the right of an edge * Creation date: (1/31/01 1:51:06 PM) * @param el HalfEdge the edge to compare p to * @param p Site the point to check * @return boolean true if p lies to the right of the edge el */ boolean right_of(HalfEdge el, Site p) { Edge e; Site topSite; boolean right_of_site, above, fast; float dxp, dyp, dxs, t1, t2, t3, yl; e = el.edge; topSite = e.reg2; right_of_site = p.x > topSite.x; if (right_of_site && el.isLeftToRight) { return true; } if (!right_of_site && !el.isLeftToRight) { return false; } if (e.a == 1.0) { dyp = p.y - topSite.y; dxp = p.x - topSite.x; fast = false; if ((!right_of_site && (e.b < 0.0)) || (right_of_site && (e.b >= 0.0))) { above = dyp >= e.b * dxp; fast = above; } else { above = p.x + p.y * e.b > e.c; if (e.b < 0.0) above = !above; if (!above) fast = true; } if (!fast) { dxs = topSite.x - e.reg1.x; above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b); if (e.b < 0.0) above = !above; } } else /*e.b==1.0 */ { yl = e.c - e.a * p.x; t1 = p.y - yl; t2 = p.x - topSite.x; t3 = yl - topSite.y; above = t1 * t1 > t2 * t2 + t3 * t3; } return (el.isLeftToRight ? above : !above); } /** * Return the list of edges * Creation date: (1/31/01 10:54:13 AM) * @return HalfEdge the edge list */ HalfEdge getEdgeList() { return leftEnd; } } /** * The EventHeap is used to handle the queue of events generated by a sweep * Creation date: (1/31/01 10:41:31 AM) * @author Luke Evans */ static class EventHeap { private HalfEdge hashTable[]; private int count; private int curMinIdx; private float minY, deltaY; void outEdge(Edge e) { } /** * Construct an EventHead from the number of Sites, minimumY and the Y delta * Creation date: (1/31/01 1:57:51 PM) * @param siteNum int the number of Sites (points) * @param minY int the minimum Y value * @param deltaY the Y delta */ EventHeap(int siteNum, float minY, float deltaY) { hashTable = new HalfEdge[siteNum * 4]; count = 0; curMinIdx = 0; this.minY = minY; this.deltaY = deltaY; } /** * Find element point in hash table * Creation date: (1/31/01 2:04:36 PM) * @param e HalfEdge the half edge * @return the index */ int hashFct(HalfEdge e) { int i = (int) ((e.yStar - minY) / deltaY * hashTable.length); if (i < 0) { i = 0; } if (i >= hashTable.length) { i = hashTable.length - 1; } if (curMinIdx > i) { curMinIdx = i; } return i; } /** * Delete an edge from the heap * Creation date: (1/31/01 2:01:44 PM) * @param e HalfEdge the edge to delete */ void deleteItem(HalfEdge e) { if (e.vertex != null) { HalfEdge item, prevItem; int idx; idx = hashFct(e); item = hashTable[idx]; prevItem = null; while (item != e) { prevItem = item; item = item.hashNext; } if (prevItem == null) { hashTable[idx] = item.hashNext; } else { prevItem.hashNext = item.hashNext; } count--; e.vertex = null; } } /** * Insert an item into the heap * Creation date: (1/31/01 2:06:21 PM) * @param e HalfEdge the item * @param v Site the point * @param offs float the offset */ void insertItem(HalfEdge e, Site v, float offs) { HalfEdge curItem, prevItem; e.vertex = v; e.yStar = v.y + offs; int idx = hashFct(e); prevItem = null; curItem = hashTable[idx]; while ((curItem != null) && ((e.yStar > curItem.yStar) || ((e.yStar == curItem.yStar) && (v.x > curItem.vertex.x)))) { prevItem = curItem; curItem = curItem.hashNext; } e.hashNext = curItem; if (prevItem == null) { hashTable[idx] = e; } else { prevItem.hashNext = e; } if (curMinIdx > idx) { curMinIdx = idx; } count++; } /** * Check if this heap is empty * Creation date: (1/31/01 2:22:43 PM) * @return boolean true if empty */ boolean isEmpty() { return (count == 0); } /** * Recalculate the minimal point * Creation date: (1/31/01 2:07:54 PM) * @return Site the minimal point */ Site recalcMin() { if (count == 0) { return null; } while (hashTable[curMinIdx] == null) { curMinIdx++; } return new Site(hashTable[curMinIdx].vertex.x, hashTable[curMinIdx].yStar); } /** * Extract the current minimal edge from the heap * Creation date: (1/31/01 2:03:06 PM) * @return HalfEdge the minimal edge */ HalfEdge extractMin() { HalfEdge curItem; if (count == 0) { return null; } recalcMin(); curItem = hashTable[curMinIdx]; hashTable[curMinIdx] = curItem.hashNext; count--; recalcMin(); // !! return curItem; } } /** * The HalfEdge class describes a vertex composed in turn of left and right edges * Creation date: (1/31/01 10:33:35 AM) * @author Luke Evans */ public static class HalfEdge { private HalfEdge leftEdge; private HalfEdge rightEdge; private Edge edge; private boolean isLeftToRight; private Site vertex; private float yStar; private HalfEdge hashNext; } /** * A Site is just a point coordinate * Creation date: (1/31/01 10:40:06 AM) * @author Luke Evans */ public static class Site { private float x, y; /** * Construct a Site from xy coords * Creation date: (1/31/01 2:25:09 PM) * @param x the X coord * @param y the Y coord */ public Site(float x, float y) { this.x = x; this.y = y; } } /** * Default constructor for a Voronoi * Creation date: (1/31/01 9:49:18 AM) */ public Voronoi() { edgeList=new EdgeList(2, 0.0f, 1.0f); } /** * Draw blinking objects * Creation date: (1/31/01 9:50:19 AM) * @param thread Thread * @param demogc Graphics * @param sweep1 HalfEdge * @param sweep2 HalfEdge * @param newBisect HalfEdge * @param s1 Site * @param s2 Site */ void blinkObjs(Thread thread,Graphics demogc, HalfEdge sweep1,HalfEdge sweep2, HalfEdge newBisect, Site s1,Site s2, // Site newvertex, Site ni,float nr, Site i1,float r1, Site i2,float r2) { int i; for (i = 0; i < 8; i++) { if (doShowVoronoi) { demogc.setColor(Color.red); if (sweep1 != null) showEdge(demogc, sweep1.edge, true, false); if (sweep2 != null) showEdge(demogc, sweep2.edge, true, false); if ((newBisect != null) && (i != 0) && (i != 7)) showEdge(demogc, newBisect.edge, true, false); } if (doShowTriangles) { demogc.setColor(Color.orange); if (sweep1 != null) showEdge(demogc, sweep1.edge, false, true); if (sweep2 != null) showEdge(demogc, sweep2.edge, false, true); if ((newBisect != null) && (i != 0) && (i != 7)) showEdge(demogc, newBisect.edge, false, true); } demogc.setColor(Color.black); if (s1 != null) { demogc.fillOval( (int)(s1.x * xScale + xOffs) - 2, (int)(s1.y * yScale + yOffs) - 2, 5,5); } if (s2 != null) { demogc.fillOval( (int)(s2.x * xScale + xOffs) - 2, (int)(s2.y * yScale + yOffs) - 2, 5,5); } if ((i1 != null) && (i != 0) && (i != 7)) { demogc.fillOval( (int)(i1.x * xScale + xOffs) - 2, (int)(i1.y * yScale + yOffs) - 2, 5, 5); demogc.drawLine( (int)(i1.x * xScale + xOffs), (int)(i1.y * yScale + yOffs), (int)(i1.x * xScale + xOffs), (int)((i1.y + r1) * yScale + yOffs)); if (doShowHeapCircles) demogc.drawArc( (int)((i1.x - r1) * xScale + xOffs), (int)((i1.y - r1) * yScale + yOffs), (int)(2.0f * r1 * xScale), (int)(2.0f * r1 * yScale), 0, 360); } if (ni != null) { demogc.fillOval( (int)(ni.x * xScale + xOffs) - 2, (int)(ni.y * yScale + yOffs) - 2, 5, 5); demogc.drawLine( (int)(ni.x * xScale + xOffs), (int)(ni.y * yScale + yOffs), (int)(ni.x * xScale + xOffs), (int)((ni.y + nr) * yScale + yOffs)); if (doShowHeapCircles) demogc.drawArc( (int)((ni.x - nr) * xScale + xOffs), (int)((ni.y - nr) * yScale + yOffs), (int)(2.0f * nr * xScale), (int)(2.0f * nr * yScale), 0, 360); } if ((i2 != null) && (i != 0) && (i != 7)) { demogc.fillOval( (int)(i2.x * xScale + xOffs) - 2, (int)(i2.y * yScale + yOffs) - 2, 5, 5); demogc.drawLine( (int)(i2.x * xScale + xOffs), (int)(i2.y * yScale + yOffs), (int)(i2.x * xScale + xOffs), (int)((i2.y + r2) * yScale + yOffs)); if (doShowHeapCircles) demogc.drawArc( (int)((i2.x - r2) * xScale + xOffs), (int)((i2.y - r2) * yScale + yOffs), (int)(2.0f * r2 * xScale), (int)(2.0f * r2 * yScale), 0, 360); } try { Thread.sleep(((i & 1) == 1) ? 300 : 100); } catch (Exception e) { } } } /** * Create a bisecting edge for points s1 and s2 * Creation date: (1/31/01 9:53:34 AM) * @param s1 Site first point * @param s2 Site second point * @return Edge the bisecting edge */ private Edge createBisect(Site s1, Site s2) { float dx, dy, a, b, c; Edge newedge; dx = s2.x - s1.x; dy = s2.y - s1.y; if (Math.abs(dx) > Math.abs(dy)) { a = 1.0f; b = dy / dx; c = s1.x + s1.y * dy / dx + (dx + dy * dy / dx) * 0.5f; } else { a = dx / dy; b = 1.0f; c = s1.x * dx / dy + s1.y + (dx * dx / dy + dy) * 0.5f; } newedge = new Edge(s1, s2, null, null, a, b, c); return newedge; } /** * Determine the distances between points s and t * Creation date: (1/31/01 9:55:06 AM) * @param s Site point 1 * @param t Site point 2 * @return float the distance */ private float dist(Site s, Site t) { float dx, dy; dx = s.x - t.x; dy = s.y - t.y; return ((float) Math.sqrt(dx * dx + dy * dy)); } /** * Eliminate sites (points) with the same coordinates from the Voronoi object * Creation date: (1/31/01 9:56:24 AM) */ private void elimDupSites() { int i, j, count; Site cursite, newsites[]; if (sites == null) return; count = 0; cursite = sites[0]; for (i = 1; i < sites.length; i++) { if ((Math.abs(cursite.x - sites[i].x) < 1e-6) && (Math.abs(cursite.y - sites[i].y) < 1e-6)) { count++; sites[i] = null; } else cursite = sites[i]; } if (count == 0) return; newsites = new Site[sites.length - count]; j = 0; for (i = 0; i < sites.length; i++) { if (sites[i] != null) { newsites[j] = sites[i]; j++; } } sites = newsites; } /** * Determine the left or right endpoint of a vertex and set this into the given Site. * Creation date: (1/31/01 9:57:56 AM) * @param e Edge the vertex * @param isLeftToRight boolean which endpoint to consider * @param s Site the Site (point) to update */ private void endpoint(Edge e, boolean isLeftToRight, Site s) { if (isLeftToRight) { e.ep1 = s; if (e.ep2 == null) { return; } } else { e.ep2 = s; if (e.ep1 == null) { return; } } HalfEdge halfEdge = new HalfEdge(); halfEdge.edge = e; halfEdge.rightEdge = resultEdgeList; resultEdgeList = halfEdge; } /** * Move all the vertices which remain after all the sweep passes (sweepStep) into the final * Voronoi diagram. * Creation date: (1/31/01 10:02:44 AM) */ private void finishSweepLine() { HalfEdge edge, nextEdge; edge = edgeList.leftEnd.rightEdge; while (edge != edgeList.rightEnd) { nextEdge = edge.rightEdge; edge.rightEdge = resultEdgeList; resultEdgeList = edge; edge = nextEdge; } edgeList.leftEnd.rightEdge = edgeList.rightEnd; edgeList.rightEnd.leftEdge = edgeList.leftEnd; } /** * Generate the Voronoi edges from an initial set of points (Sites) * Creation date: (1/31/01 10:27:57 AM) * @param sites the list of initial points */ public void generate(Site[] sites) { // Initialise and see if anything needs to be done if (initSweepLine(sites)) { // Keep sweeping until there's nothing more to do while (sweepStep(null, null)) {/* Empty body */} // Copy the resulting vertices into the finished Voronoi finishSweepLine(); } } /** * Return the half edges list * Creation date: (1/31/01 10:04:40 AM) * @return HalfEdge the linked list of half edges */ private HalfEdge getHalfEdges() { return resultEdgeList; } /** * Initialise internal structures required for the sweepline algorithm which will * compute the Voronoi diagram for all sites in initSites. * Creation date: (1/31/01 10:09:48 AM) * @param initSites Site[] the initial set of sites (points) from which to compute Voronoi * @return boolean true if ready to compute, false if nothing to do */ private boolean initSweepLine(Site[] initSites) { // Copies sites to internal array (since we sort array and eliminate // duplicates internally !) sites = new Site[initSites.length]; System.arraycopy(initSites, 0, sites, 0, initSites.length); if (sites.length == 0) return false; // Sorts and eliminates duplicates sortSites(0, sites.length - 1); elimDupSites(); minY = sites[0].y; maxY = sites[sites.length - 1].y; minX = sites[0].x; maxX = sites[0].x; for (siteIdx = 0; siteIdx < sites.length; siteIdx++) { if (minX > sites[siteIdx].x) minX = sites[siteIdx].x; if (maxX < sites[siteIdx].x) maxX = sites[siteIdx].x; } deltaY = maxY - minY; deltaX = maxX - minX; heap = new EventHeap(sites.length, minY, deltaY); edgeList = new EdgeList(sites.length, minX, deltaX); resultEdgeList = null; if (sites.length <= 1) return false; siteIdx = 0; bottomSite = sites[siteIdx]; siteIdx++; // Load first site newSite = sites[siteIdx]; siteIdx++; newIntStar = new Site(0.0f, 0.0f); return true; } /** * Find the intersection point between edges el1 and el2 * Creation date: (1/31/01 10:13:24 AM) * @param el1 HalfEdge the first edge * @param el2 HalfEdge the second edge * @return Site the intersection point */ private Site intersect(HalfEdge el1, HalfEdge el2) { // Finds the intersection between egde el1 and el2 Edge e1, e2, e; HalfEdge el; float d, xint, yint; boolean right_of_site; e1 = el1.edge; e2 = el2.edge; if ((e1 == null) || (e2 == null)) return null; if (e1.reg2 == e2.reg2) return null; d = e1.a * e2.b - e1.b * e2.a; if (-1.0e-6 < d && d < 1.0e-6) return null; xint = (e1.c * e2.b - e2.c * e1.b) / d; yint = (e2.c * e1.a - e1.c * e2.a) / d; if ((e1.reg2.y < e2.reg2.y) || ((e1.reg2.y == e2.reg2.y) && (e1.reg2.x < e2.reg2.x))) { el = el1; e = e1; } else { el = el2; e = e2; } right_of_site = (xint >= e.reg2.x); if (right_of_site == el.isLeftToRight) return null; return new Site(xint, yint); } /** * Utility method to return the left end of an edge * Creation date: (1/31/01 10:15:24 AM) * @param halfEdge HalfEdge the edge * @return Site the left end */ private Site leftReg(HalfEdge halfEdge) { if (halfEdge.edge == null) // ??? return bottomSite; // ??? if (halfEdge.isLeftToRight) return halfEdge.edge.reg1; //[0]; else return halfEdge.edge.reg2; //[1]; } /** * Utility method to return the right end of an edge * Creation date: (1/31/01 10:15:24 AM) * @param halfEdge HalfEdge the edge * @return Site the right end */ private Site rightReg(HalfEdge halfEdge) { if (halfEdge.edge == null) return bottomSite; if (halfEdge.isLeftToRight) return halfEdge.edge.reg2; //[1]; else return halfEdge.edge.reg1; //[0]; } /** * Display this Voronoi in the GC passed in * Creation date: (1/31/01 4:28:20 PM) * @param gc Graphics the graphics context in which to paint * @param showVoronoi boolean show the Voronoi edges * @param showTriangles boolean show the Delaunay edges */ public void show(Graphics gc, boolean showVoronoi, boolean showTriangles) { HalfEdge edgeList = getHalfEdges(); while (edgeList != null) { if ((edgeList.edge != null) && ((edgeList.edge.ep1 != null) || (edgeList.edge.ep2 != null) || (edgeList.isLeftToRight == false))) showEdge(gc, edgeList.edge, showVoronoi, showTriangles); edgeList = edgeList.rightEdge; } } /** * Display an edge * Creation date: (1/31/01 10:25:36 AM) */ private void showEdge(Graphics gc, Edge edge, boolean showVoronoi, boolean showTriangles) { // Show an edge on the screen. If showvoronoi==true, then the voronoi line segment // will be shown. If showtriangls==true, then the Delaunay edge will be shown. if (edge == null) return; if (showVoronoi) { Site ep1, ep2; if (edge.ep1 != null) ep1 = edge.ep1; else { if (edge.a == 1.0f) { if (edge.b >= 0) ep1 = new Site(edge.c - edge.b * yMax, yMax); else ep1 = new Site(edge.c - edge.b * yMin, yMin); } else ep1 = new Site(xMin, edge.c - edge.a * xMin); } if (edge.ep2 != null) ep2 = edge.ep2; else { if (edge.a == 1.0f) { if (edge.b < 0) ep2 = new Site(edge.c - edge.b * yMax, yMax); else ep2 = new Site(edge.c - edge.b * yMin, yMin); } else ep2 = new Site(xMax, edge.c - edge.a * xMax); } gc.drawLine((int) (ep1.x * xScale + xOffs), (int) (ep1.y * yScale + yOffs), (int) (ep2.x * xScale + xOffs), (int) (ep2.y * yScale + yOffs)); } if (showTriangles) gc.drawLine((int) (edge.reg1.x * xScale + xOffs), (int) (edge.reg1.y * yScale + yOffs), (int) (edge.reg2.x * xScale + xOffs), (int) (edge.reg2.y * yScale + yOffs)); } /** * Perform a quicksort on all sites with respect to their Y coordinate, then X coordinate. * Creation date: (1/31/01 10:26:04 AM) * @param start int beginning index of sort range * @param end int end index of sort range */ private void sortSites(int start, int end) { int i, j; Site v, tmp; if (start < end) { // vi=(start+end)/2; v = sites[(start + end) >>> 1]; // (start + end) / 2 could overflow to a negative num. Use unsigned shift right. i = start; j = end; do { while ((sites[i] != v) && ((sites[i].y < v.y) || ((sites[i].y == v.y) && (sites[i].x <= v.x)))) i++; while ((sites[j] != v) && ((sites[j].y > v.y) || ((sites[j].y == v.y) && (sites[j].x >= v.x)))) j--; if (i >= j) break; tmp = sites[i]; sites[i] = sites[j]; sites[j] = tmp; } while (true); if (sites[i] != v) System.out.println("Error pivot element !" + String.valueOf(i) + " " + String.valueOf(start) + " " + String.valueOf(end)); sortSites(start, i - 1); sortSites(i + 1, end); } } /** * Perform a single sweep step and indicate whether we are finished. * Creation date: (1/31/01 10:31:26 AM) * @param demoThread Thread * @param demoGC Graphics * @return true if there is more to do, false otherwise */ private boolean sweepStep(Thread demoThread,Graphics demoGC) { // The actual sweep step. The next sweep event is read // and the internal structures are modified. // The function returns false, if there is no such sweep event available. // (I.e. we are done) Site bot, top, temp, p, p1, v; boolean isLeftToRight; HalfEdge leftBound, rightBound, leftBound1, rightBound1; HalfEdge bisector, bisector1; Edge e; if (heap.isEmpty() == false) { newIntStar = heap.recalcMin(); } if ((newSite != null) && (heap.isEmpty() || (newSite.y < newIntStar.y) || ((newSite.y == newIntStar.y) && (newSite.x < newIntStar.x)))) { /* new site is smallest */ leftBound = edgeList.getLeftBoundEdge(newSite); rightBound = leftBound.rightEdge; bot = rightReg(leftBound); e = createBisect(bot, newSite); bisector = edgeList.createEdge(e, true); edgeList.insertEdge(leftBound, bisector); if ((p = intersect(leftBound, bisector)) != null) { heap.deleteItem(leftBound); heap.insertItem(leftBound, p, dist(p, newSite)); } bisector1 = bisector; bisector = edgeList.createEdge(e, false); edgeList.insertEdge(bisector1, bisector); if ((p1 = intersect(bisector, rightBound)) != null) heap.insertItem(bisector, p1, dist(p1, newSite)); if (demoThread != null) { blinkObjs( demoThread, demoGC, null, null, null, newSite, null, null, 0.0f, null, 0.0f, null, 0.0f); blinkObjs( demoThread, demoGC, leftBound, rightBound, null, newSite, bot, null, 0.0f, null, 0.0f, null, 0.0f); blinkObjs( demoThread, demoGC, leftBound, rightBound, bisector, newSite, bot, null, 0.0f, p, (p!=null) ? dist(p, newSite) : 0.0f, p1, (p1!=null) ? dist(p1, newSite) : 0.0f); } if (siteIdx == sites.length) newSite = null; else { newSite = sites[siteIdx]; siteIdx++; } } else { if (heap.isEmpty() != false) { // Now sweepevent available ? return false; } /* intersection is smallest */ leftBound = heap.extractMin(); leftBound1 = leftBound.leftEdge; rightBound = leftBound.rightEdge; rightBound1 = rightBound.rightEdge; bot = leftReg(leftBound); top = rightReg(rightBound); v = leftBound.vertex; if (demoThread != null) blinkObjs( demoThread, demoGC, leftBound, rightBound, null, bot, top, v, leftBound.yStar-v.y, null, 0.0f, null, 0.0f); endpoint(leftBound.edge, leftBound.isLeftToRight, v); endpoint(rightBound.edge, rightBound.isLeftToRight, v); edgeList.deleteEdge(leftBound); heap.deleteItem(rightBound); edgeList.deleteEdge(rightBound); isLeftToRight = true; if (bot.y > top.y) { temp = bot; bot = top; top = temp; isLeftToRight = false; } e = createBisect(bot, top); bisector = edgeList.createEdge(e, isLeftToRight); edgeList.insertEdge(leftBound1, bisector); endpoint(e, !isLeftToRight, v); if ((p = intersect(leftBound1, bisector)) != null) { heap.deleteItem(leftBound1); heap.insertItem(leftBound1, p, dist(p,bot)); } if ((p1 = intersect(bisector, rightBound1)) != null) { heap.insertItem(bisector, p1, dist(p1, bot)); } if (demoThread != null) { /* blinkObjs( demoThread,demoGC, leftBound,rightBound, null, bot,top, null,0.0f, null,0.0f,null,0.0f); */ blinkObjs( demoThread, demoGC, null, null, //leftBound,rightBound, bisector, bot, top, null, 0.0f, p, (p!=null) ? dist(p, bot) : 0.0f, p1, (p1!=null) ? dist(p1, bot) : 0.0f); } } return true; } /** * Create a Voronoi object with seed points chosen at random to lie within a given rectangle. * In order to achieve a reasonable distribution of points across the area, the area is * subdivided into quadrants, and pointsPerQuadrant points are generated in each quadrant. * Notice that although all the seed points will lie within the bounding rectangle, the * edges generated will extend beyond this rectangle (out to the intersection points). * Creation date: (1/31/01 5:27:24 PM) * @return Voronoi the new Voronoi object * @param area Rectangle the bounding rectangle for points * @param pointsPerQuadrant int the number of points to generate in each quadrant */ public static Voronoi makeRandomAreaVoronoi(Rectangle area, int pointsPerQuadrant) { // Create a new Voronoi Voronoi voronoi = new Voronoi(); // Generate a set of random points across the face of the gem - to ensure a // reasonable spread, we'll generate some in each quarter int midX = (int) area.getCenterX(); int midY = (int) area.getCenterY(); // Make enough points int totalPoints = pointsPerQuadrant * 4; Voronoi.Site[] sites = new Voronoi.Site[totalPoints]; // Iterate over quarter, then the number of points to do for (int i = 0; i < 4; i++) { int minX; int maxX; int minY; int maxY; float xPos, yPos; switch (i) { case 0 : // NW minX = area.x; maxX = midX; minY = area.y; maxY = midY; break; case 1 : // NE minX = midX; maxX = area.x + area.width; minY = area.y; maxY = midY; break; case 2 : // SW minX = area.x; maxX = midX; minY = midY; maxY = area.y + area.height; break; case 3 : // SE minX = midX; maxX = area.x + area.width; minY = midY; maxY = area.y + area.height; break; default : // All area minX = area.x; maxX = area.x + area.width; minY = area.y; maxY = area.y + area.height; } for (int j = 0; j < pointsPerQuadrant; j++) { xPos = ((float) ((maxX - minX) * Math.random())) + minX; yPos = ((float) ((maxY - minY) * Math.random())) + minY; sites[i * pointsPerQuadrant + j] = new Voronoi.Site(xPos, yPos); } } // Generate the Voronoi edges voronoi.generate(sites); // Return the object return voronoi; } }