/******************************************************************************* * Copyright (c) 2013 Philip Collin. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * Philip Collin - initial API and implementation ******************************************************************************/ package PaulChew; /* * Copyright (c) 2005, 2007 by L. Paul Chew. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, subject to the following * conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ import java.awt.*; import java.awt.event.*; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import javax.swing.*; /** * The Delauany applet. * * Creates and displays a Delaunay Triangulation (DT) or a Voronoi Diagram * (VoD). Has a main program so it is an application as well as an applet. * * @author Paul Chew * * Created July 2005. Derived from an earlier, messier version. * * Modified December 2007. Updated some of the Triangulation methods. Added the * "Colorful" checkbox. Reorganized the interface between DelaunayAp and * DelaunayPanel. Added code to find a Voronoi cell. * */ @SuppressWarnings("serial") public class DelaunayAp extends javax.swing.JApplet implements Runnable, ActionListener, MouseListener { private boolean debug = false; // Used for debugging private Component currentSwitch = null; // Entry-switch that mouse is in private static String windowTitle = "Voronoi/Delaunay Window"; private JRadioButton voronoiButton = new JRadioButton("Voronoi Diagram"); private JRadioButton delaunayButton = new JRadioButton("Delaunay Triangulation"); private JButton clearButton = new JButton("Clear"); private JCheckBox colorfulBox = new JCheckBox("More Colorful"); private DelaunayPanel delaunayPanel = new DelaunayPanel(this); private JLabel circleSwitch = new JLabel("Show Empty Circles"); private JLabel delaunaySwitch = new JLabel("Show Delaunay Edges"); private JLabel voronoiSwitch = new JLabel("Show Voronoi Edges"); /** * Main program (used when run as application instead of applet). */ public static void main (String[] args) { DelaunayAp applet = new DelaunayAp(); // Create applet applet.init(); // Applet initialization JFrame dWindow = new JFrame(); // Create window dWindow.setSize(700, 500); // Set window size dWindow.setTitle(windowTitle); // Set window title dWindow.setLayout(new BorderLayout()); // Specify layout manager dWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Specify closing behavior dWindow.add(applet, "Center"); // Place applet into window dWindow.setVisible(true); // Show the window } /** * Initialize the applet. * As recommended, the actual use of Swing components takes place in the * event-dispatching thread. */ public void init () { try {SwingUtilities.invokeAndWait(this);} catch (Exception e) {System.err.println("Initialization failure");} } /** * Set up the applet's GUI. * As recommended, the init method executes this in the event-dispatching * thread. */ public void run () { setLayout(new BorderLayout()); // Add the button controls ButtonGroup group = new ButtonGroup(); group.add(voronoiButton); group.add(delaunayButton); JPanel buttonPanel = new JPanel(); buttonPanel.add(voronoiButton); buttonPanel.add(delaunayButton); buttonPanel.add(clearButton); buttonPanel.add(new JLabel(" ")); // Spacing buttonPanel.add(colorfulBox); this.add(buttonPanel, "North"); // Add the mouse-entry switches JPanel switchPanel = new JPanel(); switchPanel.add(circleSwitch); switchPanel.add(new Label(" ")); // Spacing switchPanel.add(delaunaySwitch); switchPanel.add(new Label(" ")); // Spacing switchPanel.add(voronoiSwitch); this.add(switchPanel, "South"); // Build the delaunay panel delaunayPanel.setBackground(Color.gray); this.add(delaunayPanel, "Center"); // Register the listeners voronoiButton.addActionListener(this); delaunayButton.addActionListener(this); clearButton.addActionListener(this); colorfulBox.addActionListener(this); delaunayPanel.addMouseListener(this); circleSwitch.addMouseListener(this); delaunaySwitch.addMouseListener(this); voronoiSwitch.addMouseListener(this); // Initialize the radio buttons voronoiButton.doClick(); } /** * A button has been pressed; redraw the picture. */ public void actionPerformed(ActionEvent e) { if (debug) System.out.println(((AbstractButton)e.getSource()).getText()); if (e.getSource() == clearButton) delaunayPanel.clear(); delaunayPanel.repaint(); } /** * If entering a mouse-entry switch then redraw the picture. */ public void mouseEntered(MouseEvent e) { currentSwitch = e.getComponent(); if (currentSwitch instanceof JLabel) delaunayPanel.repaint(); else currentSwitch = null; } /** * If exiting a mouse-entry switch then redraw the picture. */ public void mouseExited(MouseEvent e) { currentSwitch = null; if (e.getComponent() instanceof JLabel) delaunayPanel.repaint(); } /** * If mouse has been pressed inside the delaunayPanel then add a new site. */ public void mousePressed(MouseEvent e) { if (e.getSource() != delaunayPanel) return; Pnt point = new Pnt(e.getX(), e.getY()); if (debug ) System.out.println("Click " + point); delaunayPanel.addSite(point); delaunayPanel.repaint(); } /** * Not used, but needed for MouseListener. */ public void mouseReleased(MouseEvent e) {} public void mouseClicked(MouseEvent e) {} /** * @return true iff the "colorful" box is selected */ public boolean isColorful() { return colorfulBox.isSelected(); } /** * @return true iff doing Voronoi diagram. */ public boolean isVoronoi() { return voronoiButton.isSelected(); } /** * @return true iff within circle switch */ public boolean showingCircles() { return currentSwitch == circleSwitch; } /** * @return true iff within delaunay switch */ public boolean showingDelaunay() { return currentSwitch == delaunaySwitch; } /** * @return true iff within voronoi switch */ public boolean showingVoronoi() { return currentSwitch == voronoiSwitch; } } /** * Graphics Panel for DelaunayAp. */ @SuppressWarnings("serial") class DelaunayPanel extends JPanel { public static Color voronoiColor = Color.magenta; public static Color delaunayColor = Color.green; public static int pointRadius = 3; private DelaunayAp controller; // Controller for DT private Triangulation dt; // Delaunay triangulation private Map<Object, Color> colorTable; // Remembers colors for display private Triangle initialTriangle; // Initial triangle private static int initialSize = 1000; // Size of initial triangle private Graphics g; // Stored graphics context private Random random = new Random(); // Source of random numbers /** * Create and initialize the DT. */ public DelaunayPanel (DelaunayAp controller) { this.controller = controller; initialTriangle = new Triangle( new Pnt(-3000, -3000), new Pnt( 6000, -3000), new Pnt( 0, 6000)); dt = new Triangulation(initialTriangle); colorTable = new HashMap<Object, Color>(); } /** * Add a new site to the DT. * @param point the site to add */ public void addSite(Pnt point) { dt.delaunayPlace(point); } /** * Re-initialize the DT. */ public void clear() { dt = new Triangulation(initialTriangle); } /** * Get the color for the spcified item; generate a new color if necessary. * @param item we want the color for this item * @return item's color */ private Color getColor (Object item) { if (colorTable.containsKey(item)) return colorTable.get(item); Color color = new Color(Color.HSBtoRGB(random.nextFloat(), 1.0f, 1.0f)); colorTable.put(item, color); return color; } /* Basic Drawing Methods */ /** * Draw a point. * @param point the Pnt to draw */ public void draw (Pnt point) { int r = pointRadius; int x = (int) point.coord(0); int y = (int) point.coord(1); g.fillOval(x-r, y-r, r+r, r+r); } /** * Draw a circle. * @param center the center of the circle * @param radius the circle's radius * @param fillColor null implies no fill */ public void draw (Pnt center, double radius, Color fillColor) { int x = (int) center.coord(0); int y = (int) center.coord(1); int r = (int) radius; if (fillColor != null) { Color temp = g.getColor(); g.setColor(fillColor); g.fillOval(x-r, y-r, r+r, r+r); g.setColor(temp); } g.drawOval(x-r, y-r, r+r, r+r); } /** * Draw all the Delaunay triangles. * @param withFill true iff drawing Delaunay triangles with fill colors */ public void drawAllDelaunay (boolean withFill) { ignorePnts.clear(); for (Triangle triangle : dt) { Pnt[] vertices = triangle.toArray(new Pnt[0]); draw(vertices, withFill? getColor(triangle) : null); } } ArrayList<Pnt[]> ignorePnts = new ArrayList<Pnt[]>(); public boolean checkIgnored(Pnt p1, Pnt p2) { for (Pnt[] p : ignorePnts) { if (p[0].equals(p1) && p[1].equals(p2)) { return false; } else if (p[0].equals(p2) && p[1].equals(p1)) { return false; } } return true; } /** * Draw a polygon. * @param polygon an array of polygon vertices * @param fillColor null implies no fill */ public void draw (Pnt[] polygon, Color fillColor) { int ignore = 0; double dist = 0; dist = Math.pow(2, polygon[0].coord(0)-polygon[1].coord(0))+Math.pow(2, polygon[0].coord(1)-polygon[1].coord(1)); double temp = Math.pow(2, polygon[0].coord(0)-polygon[2].coord(0))+Math.pow(2, polygon[0].coord(1)-polygon[2].coord(1)); if (dist < temp) { dist = temp; ignore = 1; } temp = Math.pow(2, polygon[1].coord(0)-polygon[2].coord(0))+Math.pow(2, polygon[1].coord(1)-polygon[2].coord(1)); if (dist < temp) { dist = temp; ignore = 2; } if (ignore != 0 && checkIgnored(polygon[0], polygon[1])) { drawL(polygon[0], polygon[1]); } else { ignorePnts.add(new Pnt[]{polygon[0], polygon[1]}); } if (ignore != 1 && checkIgnored(polygon[0], polygon[2])) { drawL(polygon[0], polygon[2]); } else { ignorePnts.add(new Pnt[]{polygon[0], polygon[2]}); } if (ignore != 2 && checkIgnored(polygon[1], polygon[2])) { drawL(polygon[1], polygon[2]); } else { ignorePnts.add(new Pnt[]{polygon[1], polygon[2]}); } } public void drawL(Pnt p1, Pnt p2) { if (p1.coord(0) < 0 || p2.coord(0) < 0) { return; } if (p1.coord(1) < 0 || p2.coord(1) < 0) { return; } if (p1.coord(0) > 1000 || p2.coord(0) > 1000) { return; } if (p1.coord(1) > 1000 || p2.coord(1) > 1000) { return; } g.drawLine((int)p1.coord(0), (int)p1.coord(1), (int)p2.coord(0), (int)p2.coord(1)); } /* Higher Level Drawing Methods */ /** * Handles painting entire contents of DelaunayPanel. * Called automatically; requested via call to repaint(). * @param g the Graphics context */ public void paintComponent (Graphics g) { super.paintComponent(g); this.g = g; // Flood the drawing area with a "background" color Color temp = g.getColor(); if (!controller.isVoronoi()) g.setColor(delaunayColor); else if (dt.contains(initialTriangle)) g.setColor(this.getBackground()); else g.setColor(voronoiColor); g.fillRect(0, 0, this.getWidth(), this.getHeight()); g.setColor(temp); // If no colors then we can clear the color table if (!controller.isColorful()) colorTable.clear(); // Draw the appropriate picture if (controller.isVoronoi()) drawAllVoronoi(controller.isColorful(), true); else drawAllDelaunay(controller.isColorful()); // Draw any extra info due to the mouse-entry switches temp = g.getColor(); g.setColor(Color.white); if (controller.showingCircles()) drawAllCircles(); if (controller.showingDelaunay()) drawAllDelaunay(false); if (controller.showingVoronoi()) drawAllVoronoi(false, false); g.setColor(temp); } /** * Draw all the Voronoi cells. * @param withFill true iff drawing Voronoi cells with fill colors * @param withSites true iff drawing the site for each Voronoi cell */ public void drawAllVoronoi (boolean withFill, boolean withSites) { // Keep track of sites done; no drawing for initial triangles sites HashSet<Pnt> done = new HashSet<Pnt>(initialTriangle); for (Triangle triangle : dt) for (Pnt site: triangle) { if (done.contains(site)) continue; done.add(site); List<Triangle> list = dt.surroundingTriangles(site, triangle); Pnt[] vertices = new Pnt[list.size()]; int i = 0; for (Triangle tri: list) vertices[i++] = tri.getCircumcenter(); draw(vertices, withFill? getColor(site) : null); if (withSites) draw(site); } } /** * Draw all the empty circles (one for each triangle) of the DT. */ public void drawAllCircles () { // Loop through all triangles of the DT for (Triangle triangle: dt) { // Skip circles involving the initial-triangle vertices if (triangle.containsAny(initialTriangle)) continue; Pnt c = triangle.getCircumcenter(); double radius = c.subtract(triangle.get(0)).magnitude(); draw(c, radius, null); } } }