Search code examples
javaswinggraphicscomputational-geometryswingworker

repainting an applet from a swingworker used to compute triangles and circum-circles


I am trying to replicate the applet found here as a part of an exercise. The applet is using Fortune's algorithm to generate both; a Voronoi diagram and Delaunay triangulation. I am just interested in generating the Delaunay Triangulation in a plane and thus, would be using the incremental algorithms i.e. adding 1 point at a time. I intend to show the triangles being generated at every stage when a sample point is added.

I am using a SwingWorker class to create an instance of the Triangulate class which contains the algorithm. I am calling the triangulate method inside a for loop which iterates through the set of sample points when the start button on the GUI is clicked.

Here's the code for that:

JButton startButton = new JButton("Start");
        startButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                SwingWorker<List<Triangle>, Triangle> worker = new SwingWorker<List<Triangle>, Triangle>() {
                    @Override
                    protected List<Triangle> doInBackground() throws Exception {
                        Triangulate dt = new Triangulate(drawingPanel.pointsList());
                        dt.preTriangulate(); //Set-up a bounding triangle and obtain a random permutation of the points
                        List<PlanarPoint> pointsList = dt.pointsList();
                        for (int i = 0; i < pointsList.size(); i++) {
                            PlanarPoint sample = pointsList.get(i);
                            dt.triangulate(sample); 
                            List<Triangle> list = dt.trianglesList(); //Obtaining the list of triangles at every stage. Good Idea??
                            for (int j = 0; j < list.size(); j++) {
                                publish(list.get(j));
                            }
                            Thread.sleep(500);
                        }
                        dt.removeTriangles(dt.trianglesList()); // Remove all the triangles containing bounding-triangle vertices
                        return dt.trianglesList();
                    }

                    protected void process(List<Triangle> triangles) {
                        for (Triangle triangle : triangles) {
                            g = drawingPanel.getGraphics();
                            PlanarPoint p1 = triangle.getVertex1();
                            PlanarPoint p2 = triangle.getVertex2();
                            PlanarPoint p3 = triangle.getVertex3();
                            g.drawLine((int) Math.ceil(p1.x), (int) Math.ceil(p1.y),
                                    (int) Math.ceil(p2.x), (int) Math.ceil(p2.y));
                            g.drawLine((int) Math.ceil(p2.x),(int) Math.ceil(p2.y),
                                    (int) Math.ceil(p3.x),(int) Math.ceil(p3.y));
                            g.drawLine((int) Math.ceil(p3.x),(int) Math.ceil(p3.y),
                                    (int) Math.ceil(p1.x),(int) Math.ceil(p1.y));
                        }
                    }
                };
                worker.execute();
            }
        });

Here is the Triangulate class which computes a Delanuay Triangulation of a set of points:

public class Triangulate {

    private List<PlanarPoint> pointsList;
    private List<Triangle> triangleList;
    private Triangle boundingTriangle;
    private List<Edge> edgeList;

    public Triangulate(List<PlanarPoint> pointsList) {
        this.pointsList = pointsList;
        this.triangleList = new ArrayList<Triangle>();
        this.edgeList = new ArrayList<Edge>();
    }

    public List<Triangle> trianglesList() {
        return triangleList;
    }

    public List<PlanarPoint> pointsList() {
        return pointsList;
    }

    public void preTriangulate() {
        boundingTriangle = getBoundingTriangle(pointsList);
        triangleList.add(boundingTriangle);
        randomPermutation(pointsList);
    }

    public void triangulate(PlanarPoint samplePoint) {
        // A procedure implementing the Bowyer - Watson algorithm
        // to calculate the DT of a set of points in a plane.
    }

    public void removeTriangles(List<Triangle> trianglesList) {
        // A procedure to remove all triangles from the list sharing
        // edges with the bounding-triangle
    }

    private Triangle getBoundingTriangle(List<PlanarPoint> pointsList) {
        //Obtains a bounding-triangle for a set of points
    }

    public void randomPermutation(List<PlanarPoint> pointsList) {
        //Obtains a random permutation of a set of points
    }
}

I have 3 other classes

  1. PlanarPoint - sub-class of Point2D.Double which implements Comparable to provide a y-co-ordinate based sorting
  2. Triangle - A class which determines a circum-circle and circum-radius for the triangle and determines whether a point lies inside the circumcircle of the triangle
  3. Edge - A class which represents Edge as the one having 2 PlanarPoints as its end-points.
  4. DrawingPanel - A class which acts as the surface on which points are added at click events and drawn on the screen.

    Now, here are a few concerns which I have

    1. Is there a better way to show the triangles and possibly circum-circles by iterating over a set of points and then calling a function of the Triangulate class to get the existing circum-circles and triangles
    2. Should all the drawing be restricted to the DrawingPanel class since in the code snippets above I am painting in the class which extends JApplet/JFrame and thus whenever the window is resized, the drawn triangles are lost? Is there a design pattern which I can follow?
    3. Is the usage of SwingWorker over spawning another thread justified over here except for the fact that the time to compute the DT of a set of points is a time-consuming task?

If I have missed any details, please let me know

Thanks, Chaitanya


Solution

  • Suggestions:

    • Don't use getGraphics() to get a Graphics object since the Graphics object obtained won't persist if any repaint is performed (something out of your control). Instead draw to a BufferedImage and have the JPanel or JComponent draw the BufferedImage in its paintComponent override, or add your image data to a Collection of some sort, and have the paintComponent override method iterate through the Collection using the information to draw your images.
    • Don't draw directly in a top level window such as a JFrame or JApplet, but instead in a component that derives from JComponent, often either JComponent itself or JPanel.
    • Read the Swing graphics tutorials as they will explain all of this and more.
    • SwingWorker is fully justified since you want to create a thread that is background to a Swing application yet interacts with the Swing application -- the very situation that SwingWorkers were created for.