确定弧是否包含/覆盖另一个弧

时间:2016-03-14 12:06:32

标签: java geometry graphics2d

首先,我使用Graphics drawArc和fillArc方法随机绘制两个弧。一个弧,比如说arc1比另一个弧大,比如说arc2。 现在我想看看arc1是否包含(全部或部分)arc2。我尝试了各种各样的方法但无济于事。例如,首先计算它们之间的距离,然后取这两者的点积,并观察它是否大于第一个弧的半径乘以其方向的余弦。 仍然没有成功,任何帮助或建议将不胜感激。 是否有更好/另一种方法来实现这一目标? 是否也可以估计arc1覆盖多少arc2?感谢,

2 个答案:

答案 0 :(得分:1)

我会给你一个简单的解决方案,可以计算任何形状 - 不仅仅是弧形:

  public Vector measureArea(int[] pix) {
    int i;
    Vector v=new Vector();
    for(i=0; i<pix.length; i++)
      if((pix[i]&0x00ffffff)==0x00000000) v.add(i);
    return v;
  }

这会找到属于该区域的像素:您可以按如下方式填充弧,然后调用此函数:

BufferedImage bim=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g=bim.getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, w, h);
g.setColor(Color.black);
g2.fillArc(x, y, 2*w/16, 2*h/16, 270, 250);
int[] pix=bim.getRGB(0, 0, w, h, null, 0, w);
Vector v=measureArea(pix);

重复第二个弧,然后找到共同点。

for(i=0; i<v.size(); i++) {
  int I=((Integer)v.get(i)).intValue();
  for(j=0; j<v2.size(); j++) {
    int J=((Integer)v2.get(j)).intValue();
    if(I==J) ..... // do something
  }
}

如果你想要更多的数学方法,你必须用圆(或两个楔)来定义实心弧,并找到相交这些形状的区域。

第三种方法是使用java中的区域。

Area a=new Area(new Arc2D.Double(x+3*w/4-w/16, y+h/4-h/16, 2*w/16, 2*h/16, 270, 250, Arc2D.OPEN));
Area a2=new Area(new Arc2D.Double(x+3*w/4, y+h/4, 2*w/16, 2*h/16, 270, 200, Arc2D.OPEN));
Area intrsct=new Area(new Arc2D.Double(x+3*w/4-w/16, y+h/4-h/16, 2*w/16, 2*h/16, 270, 250, Arc2D.OPEN));
intrsct.intersect(a2);

现在intrsct有了交集。

如果我们将其扩展为简单形状,我们有:

Arc2D.Double a=new Arc2D.Double(x+3*w/4-w/16, y+h/4-h/16, 2*w/16, 2*h/16, 270, 250, Arc2D.OPEN);
Arc2D.Double a2=new Arc2D.Double(x+3*w/4, y+h/4, 2*w/16, 2*h/16, 270, 200, Arc2D.OPEN);
Rectangle b=a.getBounds();
int intrsct=0;
for(i=0; i<b.getWidth(); i++)
for(j=0; j<b.getHeight(); j++)
  if(a.contains(b.x+i, b.y+j) && a2.contains(b.x+i, b.y+j)) intrsct++;

第四种方法。

-

如果您想要具有给定颜色的圆弧,则需要在第一种方法中检查该颜色。所以我们改变测量区域如下:

  public Vector measureArea(int[] pix, int color) {
    int i;
    Vector v=new Vector();
    int c=color&0x00ffffff;
    for(i=0; i<pix.length; i++)
      if((pix[i]&0x00ffffff)==c) v.add(i);
    return v;
  }

并将其称为measureArea(pix,Color.red.getRGB())。例如。

并确保清除每个形状的图像,以便自行计算:

 public Image init( Graphics g )
    {
         bim=new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
         g=bim.getGraphics();
         g.setColor(Color.yellow);
         g.fillRect(0, 0, w, h);
         g.setColor(Color.red);
         g.fillArc(x, y, 300, 300, 270, 75);  // 2*w/16, 2*h/16 
         int[] pix=bim.getRGB(0, 0, w, h, null, 0, w);
         Vector v1=measureArea(pix, Color.red.getRGB());
         g.setColor(Color.yellow);
         g.fillRect(0, 0, w, h);
         g.setColor(Color.blue);
         g.fillArc(x+100, y+100, 150, 150, 270, 45); //2*w/32, 2*h/32,
         pix=bim.getRGB(0, 0, w, h, null, 0, w);
         Vector v2=measureArea(pix, Color.blue.getRGB());
         System.out.println( intersect(v1, v2) );
         return bim;
    }

注3:带有区域的方法与颜色无关 - 如果有效则使用它。 如果您有复杂的形状,可以在以后使用带像素的方法:

要将所有形状绘制在一起,只需执行您现在所做的操作:将它们保存在一个图像中。要测量区域,请使用另一个图像bim2连续绘制每个形状,调用测量区域功能清除图像等 - 它不必显示在任何位置 - 您有另一个图像一起显示所有形状。我希望这有效。

答案 1 :(得分:1)

answer by gpash列出了几个选项。正如评论中所提到的,我建议基于Area的apprach用于通用案例。虽然区域计算(例如computing the intersection)(对于此示例)可能很昂贵,但它们可能是基于图像和纯粹分析方法之间的良好权衡:

  • 基于图像的方法提出了一些问题,例如:关于图像大小。另外,对于“大”形状,运行时和存储器消耗可能很大(想象覆盖例如1000×1000像素的区域的形状)。
  • 纯粹的分析解决方案可能在数学上相当复杂。人们可以考虑将其分解为更简单的任务,它肯定是可行的,但不是微不足道的。也许更重要的是:此方法不会推广其他Shape类型。

使用基于Area的解决方案,计算两个任意形状s0s1(可能是Arc2D任何)之间的交集其他形状)相当简单:

    Area a = new Area(s0);
    a.intersect(new Area(s1));

(就是这样)。

附注:可以考虑进行保守测试:如果边界体积相交,则形状可以相交。因此,对于某些用例,可以考虑做这样的事情:

Shape s0 = ...;
Shape s1 = ...;
if (!s0.getBounds().intersects(s1.getBounds()))
{
    // The bounds do not intersect. Then the shapes 
    // can not intersect.
    return ...;
}
else
{
    // The bounds DO intesect. Perform the Area-based 
    // intersection computation here:
    ...
}

剩下的是计算Area的区域 - 即交叉区域的大小Area类有一个方法可用于检查区域isEmpty。但它没有计算区域大小的方法。但是,这可以通过使用(展平!)PathIterator将结果区域转换为多边形,然后计算多边形区域来计算,例如在the answers to this question中。

可能对此非常棘手的是,通常情况下,区域可以签名(也就是说,它们可以正面,取决于多边形的顶点是分别以逆时针还是顺时针顺序给出的)。此外,两个形状之间的交点不一定会形成单个连接的形状,但可能会导致不同的闭合区域,如下图所示:

IntersectionAreas

图像是来自以下MCVE的屏幕截图,它允许使用鼠标拖动给定的形状,并打印形状的区域及其交集。

这使用一些实用方法进行区域计算,这些方法一般来自几何的一组实用程序,特别是shapes,我刚开始收集它们) < / p>

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.Area;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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


public class ShapeIntersectionAreaTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        f.getContentPane().add(new ShapeIntersectionAreaTestPanel());

        f.setSize(800,800);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}


class ShapeIntersectionAreaTestPanel extends JPanel
    implements MouseListener, MouseMotionListener
{
    private Shape shape0;
    private Shape shape1;
    private Shape draggedShape;
    private Point previousMousePosition;

    ShapeIntersectionAreaTestPanel()
    {
        shape0 = new Arc2D.Double(100, 160, 200, 200, 90, 120, Arc2D.PIE);
        shape1 = new Arc2D.Double(300, 400, 100, 150, 220, 260, Arc2D.PIE);

        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.RED);
        g.fill(shape0);
        g.setColor(Color.BLUE);
        g.fill(shape1);

        Shape intersection = 
            ShapeIntersectionAreaUtils.computeIntersection(shape0, shape1);
        g.setColor(Color.MAGENTA);
        g.fill(intersection);

        double area0 = Math.abs( 
            ShapeIntersectionAreaUtils.computeSignedArea(shape0, 1.0));
        double area1 = Math.abs( 
            ShapeIntersectionAreaUtils.computeSignedArea(shape1, 1.0));
        double areaIntersection = Math.abs( 
            ShapeIntersectionAreaUtils.computeSignedArea(intersection, 1.0));
        g.setColor(Color.BLACK);
        g.setFont(new Font("Monospaced", Font.PLAIN, 12));
        g.drawString(String.format("Red area         : %10.3f", area0), 10, 20);
        g.drawString(String.format("Blue area        : %10.3f", area1), 10, 40);
        g.drawString(String.format("Intersection area: %10.3f", areaIntersection), 10, 60);
    }


    @Override
    public void mouseDragged(MouseEvent e)
    {
        int dx = e.getX() - previousMousePosition.x;
        int dy = e.getY() - previousMousePosition.y;
        AffineTransform at = 
            AffineTransform.getTranslateInstance(dx, dy);
        if (draggedShape == shape0)
        {
            shape0 = at.createTransformedShape(draggedShape);
            draggedShape = shape0;
        }
        if (draggedShape == shape1)
        {
            shape1 = at.createTransformedShape(draggedShape);
            draggedShape = shape1;
        }
        repaint();
        previousMousePosition = e.getPoint();
    }

    @Override
    public void mouseMoved(MouseEvent e)
    {
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
        draggedShape = null;
        if (shape0.contains(e.getPoint()))
        {
            draggedShape = shape0;
        }
        if (shape1.contains(e.getPoint()))
        {
            draggedShape = shape1;
        }
        previousMousePosition = e.getPoint();
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggedShape = null;
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
    }

}

// Utility methods related to shape and shape area computations, mostly taken from 
// https://github.com/javagl/Geom/blob/master/src/main/java/de/javagl/geom/Shapes.java
class ShapeIntersectionAreaUtils
{
    public static Shape computeIntersection(Shape s0, Shape s1)
    {
        Area a = new Area(s0);
        a.intersect(new Area(s1));
        return a;
    }


    /**
     * Compute all closed regions that occur in the given shape, as
     * lists of points, each describing one polygon
     * 
     * @param shape The shape
     * @param flatness The flatness for the shape path iterator
     * @return The regions
     */
    static List<List<Point2D>> computeRegions(
        Shape shape, double flatness)
    {
        List<List<Point2D>> regions = new ArrayList<List<Point2D>>();
        PathIterator pi = shape.getPathIterator(null, flatness);
        double coords[] = new double[6];
        List<Point2D> region = Collections.emptyList();
        while (!pi.isDone())
        {
            switch (pi.currentSegment(coords))
            {
                case PathIterator.SEG_MOVETO:
                    region = new ArrayList<Point2D>();
                    region.add(new Point2D.Double(coords[0], coords[1]));
                    break;

                case PathIterator.SEG_LINETO:
                    region.add(new Point2D.Double(coords[0], coords[1]));
                    break;

                case PathIterator.SEG_CLOSE:
                    regions.add(region);
                    break;

                case PathIterator.SEG_CUBICTO:
                case PathIterator.SEG_QUADTO:
                default:
                    throw new AssertionError(
                        "Invalid segment in flattened path");
            }
            pi.next();
        }
        return regions;
    }

    /**
     * Computes the (signed) area enclosed by the given point list.
     * The area will be positive if the points are ordered 
     * counterclockwise, and and negative if the points are ordered 
     * clockwise.
     * 
     * @param points The points
     * @return The signed area
     */
    static double computeSignedArea(List<? extends Point2D> points)
    {
        double sum0 = 0;
        double sum1 = 0;
        for (int i=0; i<points.size()-1; i++)
        {
            int i0 = i;
            int i1 = i + 1;
            Point2D p0 = points.get(i0);
            Point2D p1 = points.get(i1);
            double x0 = p0.getX();
            double y0 = p0.getY();
            double x1 = p1.getX();
            double y1 = p1.getY();
            sum0 += x0 * y1;
            sum1 += x1 * y0;
        }
        Point2D p0 = points.get(0);
        Point2D pn = points.get(points.size()-1);
        double x0 = p0.getX();
        double y0 = p0.getY();
        double xn = pn.getX();
        double yn = pn.getY();
        sum0 += xn * y0;
        sum1 += x0 * yn;
        double area = 0.5 * (sum0 - sum1);
        return area;
    }

    /**
     * Compute the (signed) area that is covered by the given shape.<br>
     * <br>
     * The area will be positive for regions where the points are 
     * ordered counterclockwise, and and negative for regions where 
     * the points are ordered clockwise.
     * 
     * @param shape The shape
     * @param flatness The flatness for the path iterator
     * @return The signed area
     */ 
    public static double computeSignedArea(Shape shape, double flatness)
    {
        double area = 0;
        List<List<Point2D>> regions = computeRegions(shape, flatness);
        for (List<Point2D> region : regions)
        {
            double signedArea = computeSignedArea(region);
            area += signedArea;
        }
        return area;
    }
}

(注意:拖动形状的机制并不是特别优雅。在实际应用中,这应该以不同的方式解决 - 这只是用于区域计算方法的演示)