Unexpected results from Area.intersect()

33 Views Asked by At

I am attempting to get the intersection of two polygons via Area.intersect(), but in some cases I am getting a strange result.

My code:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame();
        frame.setSize(400, 400);
        frame.setLocationRelativeTo(null);
        frame.setResizable(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);

        JPanel panel = new Panel();
        frame.add(panel);
    }

    static class Panel extends JPanel
    {
        Panel()
        {
            setBackground(new Color(0, 50, 0));
            setFocusable(true);
        }

        @Override
        public void paintComponent(Graphics g1)
        {
            super.paintComponent(g1);
            Graphics2D g = (Graphics2D) g1;
            
            AffineTransform at = new AffineTransform();
            at.scale(3,3);
            g.transform(at);
            
            g.setColor(Color.blue);
            
            Path2D.Double path1 = new Path2D.Double();
            
            path1.append(new Line2D.Double(76,  62,                 31, 62                  ),true);
            path1.append(new Line2D.Double(31,  62,                 31, 70.00000000000001   ),true);
            path1.append(new Line2D.Double(31,  70.00000000000001,  76, 70                  ),true);
            
            Area area1 = new Area(path1);

            Path2D.Double path2 = new Path2D.Double();
            
            path2.append(new Line2D.Double(52, 10,52,130),true);
            path2.append(new Line2D.Double(52, 130,60,130),true);
            path2.append(new Line2D.Double(60, 130,60.0,10),true);

            Area area2 = new Area(path2);
            
            g.fill(area1);
            
            g.setColor(Color.red);
            g.fill(area2);
            
            Area areaIntersect = new Area(path1);
            areaIntersect.intersect(area2);
            
            g.setColor(Color.yellow);
            g.draw(areaIntersect);
            
            PathIterator pi = areaIntersect.getPathIterator(null);

            while(!pi.isDone())
            {
                double[] coords = new double[6];
                switch(pi.currentSegment(coords))
                {
                    case PathIterator.SEG_CLOSE:
                        break;
                    case PathIterator.SEG_CUBICTO:
                        System.out.println(coords[4] + "," + coords[5]);
                    case PathIterator.SEG_QUADTO:
                        System.out.println(coords[2] + "," + coords[3]);
                    case PathIterator.SEG_MOVETO:
                    case PathIterator.SEG_LINETO:
                        System.out.println(coords[0] + "," + coords[1]);
                }
                pi.next();
            }
        }
    }
}

and the result:

enter image description here

The intersection should just be a yellow square; the long yellow horizontal line should not be part of the result.

I know that the problem relates to the very tiny angle of the bottom of the blue box: Line2D.Double(31,70.00000000000001,76,70). If I change 70.00000000000001 to nearly any other value, the intersection box displays correctly. For example, changing both of them to 70.000000000001:

            path1.append(new Line2D.Double(31,  62,                 31, 70.000000000001 ),true);
            path1.append(new Line2D.Double(31,  70.000000000001,    76, 70                  ),true);

enter image description here

I printed the coordinates of the generated intersect, to make sure what I was seeing was not a display problem, but it clearly shows the line from 31.0 to 76.0 on the x axis, whereas it should only be 52.0 to 60.0.

52.0,60.0
52.0,70.00000000000001
31.0,70.00000000000001
76.0,70.0
60.0,70.0
60.0,60.0

I am doing some trigonometry to generate the polygons, so values like .00000000000001 are expected. I could round them off, but without knowing what's causing the problem, I'm worried that it might occur with other values.

Update using MadProgrammer's answer but changing path1 to:

    Path2D.Double path1 = new Path2D.Double();
    path1.append(new Line2D.Double(31, 62,                  76, 62), true);
    path1.append(new Line2D.Double(76, 62,                  76, 70.0000000000001), true);
    path1.append(new Line2D.Double(76, 70.0000000000001,    31, 70), true);
    path1.append(new Line2D.Double(31, 70, 31, 62), true);
    path1.closePath();

..produces a slightly less-wrong result:

enter image description here

1

There are 1 best solutions below

4
MadProgrammer On

First, I removed the scaling, that didn't help.

Next I added closePath to both the paths, that didn't help.

Next, I add an additional line to each path to ensure that the paths were closed correctly, that didn't help.

Next I used Rectangle2D instead of Path and solved the issue. So the problem lies with the Paths.

Next, I changed the intersection to use the pre-existing Areas instead of the first Path and the second Area.

Area areaIntersect = new Area(area1);
areaIntersect.intersect(area2);

this didn't help.

Now I'm scratching my head. I went back and compared the Path with the Rectangle2D and noted that path one "seems" to have a counter clock wise winding, so I changed it to have a clock wise winding instead...

Path2D.Double path1 = new Path2D.Double();
path1.append(new Line2D.Double(31, 62, 76, 62), true);
path1.append(new Line2D.Double(76, 62, 76, 70.00000000000001), true);
path1.append(new Line2D.Double(76, 70.00000000000001, 31, 70.00000000000001), true);
path1.append(new Line2D.Double(31, 70.00000000000001, 31, 62), true);
path1.closePath();

and that worked!

There also could be an issue with the difference between 70 and 70.00000000000001 although in my updated test, it doesn't seem to make a difference.

Why would the winding matter? In this case, I can't say, but it just seems to make a difference.

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Main {

    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setSize(400, 400);
                frame.setLocationRelativeTo(null);

                JPanel panel = new TestPane();
                frame.add(panel);
                frame.setVisible(true);
            }
        });
    }

    class TestPane extends JPanel {

        TestPane() {
            setBackground(new Color(0, 50, 0));
            setFocusable(true);
        }

        @Override
        public void paintComponent(Graphics g1) {
            super.paintComponent(g1);
            Graphics2D g = (Graphics2D) g1;

            AffineTransform at = new AffineTransform();
            at.scale(3, 3);
            g.transform(at);

            Path2D.Double path1 = new Path2D.Double();
            path1.append(new Line2D.Double(31, 62, 76, 62), true);
            path1.append(new Line2D.Double(76, 62, 76, 70.00000000000001), true);
            path1.append(new Line2D.Double(76, 70.00000000000001, 31, 70.00000000000001), true);
            path1.append(new Line2D.Double(31, 70, 31, 62), true);
            path1.closePath();

            Path2D.Double path2 = new Path2D.Double();

            path2.append(new Line2D.Double(52, 10, 52, 130), true);
            path2.append(new Line2D.Double(52, 130, 60, 130), true);
            path2.append(new Line2D.Double(60, 130, 60.0, 10), true);
            path2.append(new Line2D.Double(60.0, 10, 52, 10), true);
            path2.closePath();

            Area area1 = new Area(path1);
            Area area2 = new Area(path2);

            g.setColor(Color.blue);
            g.draw(area1);

            g.setColor(Color.red);
            g.draw(area2);

            Area areaIntersect = new Area(area1);
            areaIntersect.intersect(area2);

            g.setColor(Color.yellow);
            g.draw(areaIntersect);
        }
    }
}