检测闭合Bezier曲线中的自交叉

时间:2010-01-26 12:24:47

标签: java algorithm geometry bezier java-2d

我通过将立方贝塞尔曲线拼接在一起创建了“blob”形状(截图如下)。我希望能够检测出曲线越过自身或其他曲线的情况,并且想知道是否有推荐的方法或已知的算法来做到这一点?

我的一个想法是使用FlatteningPathIterator将形状分解为直线段,然后检测给定的段是否与另一个段相交,但我对是否有更好的方法感兴趣(如这将具有二次性能)。如果我确实追求这种方法,那么Java中的库函数是否可以检测两个线段是否重叠?

感谢。

无交叉

No Crossover http://www.freeimagehosting.net/uploads/7ad585414d.png

跨接

Crossover http://www.freeimagehosting.net/uploads/823748f8bb.png

5 个答案:

答案 0 :(得分:4)

我实际上找到了一个使用内置Java2D功能的工作解决方案,而且非常快......

只需从曲线中创建一个Path2D,然后从Path2D中创建一个区域并调用方法Area.isSingular();

它有效......请参阅这个小例子。

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Path2D;

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



public class Test {
@SuppressWarnings("serial")
public static void main(String[] args) {
    JFrame f = new JFrame("Test");
    JPanel c = new JPanel() {
        Area a;
        Path2D p;
        {
            p = new Path2D.Double();
            p.append(new CubicCurve2D.Double(0, 0, 100, 0, 150, 50, 200, 100), true);
            p.append(new CubicCurve2D.Double(200, 100, 200, 150, 150,0, 50, 100), true);
            p.append(new CubicCurve2D.Double(100, 100, 100, 50, 50, 50, 0, 0), true);
            a = new Area(p);
            setPreferredSize(new Dimension(300, 300));
        }
        @Override
        protected void paintComponent(Graphics g) {
            g.setColor(Color.black);
            ((Graphics2D)g).fill(p);
            System.out.println(a.isSingular());
        }
    };
    f.setContentPane(c);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.pack();
    f.setVisible(true);
}
}

答案 1 :(得分:2)

您可以采取Bezier curve的矢量函数:

alt text

将成对曲线的不同贝塞尔曲线等同起来,看看[0,1]中是否有解。当然,在重叠的部分是不同曲线的一部分的情况下,这将有所帮助。在一条曲线与自身相交的情况下,它不会有帮助......

编辑:

我引用了二次曲线函数,所以这是立方体: alt text

确实难以解决这个问题。作为替代方案,我建议使用更松散的方法。你可以做的是将每条曲线“分割”成n个点并使用上面的函数计算它们的位置。然后,对于每个点,您将考虑任意半径的磁盘(取决于曲线的大小)并搜索这些磁盘的交叉点。您需要忽略顺序磁盘的交叉点,因为相交只是因为它们在单个曲线段上彼此靠得太近。

此方法应该非常快,但如果选择“错误”参数(n尺寸和半径),则可能会失去准确性,但您可以进行实验。

答案 2 :(得分:1)

我不确定这是否有帮助,但它类似于多边形渲染中的问题,对于每条扫描线Y,您有一个(X,flag)值对的数组,其中线穿过该扫描线。

跟随形状中的每条曲线,以及它穿过每条扫描线Y的位置,记录(X,标志),其中flag = 1,如果“north”,则flag = -1,如果“向南”。

如果在每条扫描线上您按X顺序考虑对,并保持标志值的运行总和,那么如果曲线是顺时针,则两个X值的跨度之间的总和将为正,如果是曲线则为负是逆时针的。

如果所有跨距均为+1或所有跨距均为-1,则曲线不会自行交叉。

编辑:这需要时间与图形越过的扫描线数呈线性关系。然后,结果数据结构可用于渲染图形。

答案 3 :(得分:1)

我认为你可以通过

得到一个不错的近似值
  • 使用FlatteningPathIterator获取近似于blob的多边形。
  • 将多边形周围的路径划分为非减少 y 的子路径(即向下路径 - 想象只使用铅笔的向下笔划绘制多边形)。
  • 一致地向下走路,将每个线段仅与 y 维度中至少重叠的线段进行比较。

这非常简单,避免了您担心的O( n 2 )性能。对于您的平均基本blob,如图中的那些,只有两条向下的路径。

您可以通过保持向下路径水平排序(可能是TreeSet)来进一步减少比较次数。

仅比较 y 维度中重叠的线段的另一种方法是使用interval tree

答案 4 :(得分:0)

这是来自Prof. Georg Umlauf

讲座的一些递归算法
INTERSECT(b_0,...,b_m;c_0,...,c_n, EPSILON)
  if [min b_i, max b_i] AND [min c_i, max c_i] != EMPTY { // check bounding boxes
    if m*(m-1)*max||delta^2(b_i)|| > EPSILON) { // check flatness
      Calculate b'_0, ..., b'_2m over [0, 0.5, 1] with the deCasteljau algorithm;
      INTERSECT(b'_0,...,b'_m;c_0,...,c_n;EPSILON);
      INTERSECT(b'_m,...,b'_2m;c_0,...,c_n;EPSILON);
    }
  }
  else {
    if (n*n-1)*max||delta^2(c_i)|| > EPSILON then {
      Calculate c'_0, ..., c'_2m over [0, 0.5, 1] with the deCasteljau algorithm;
      INTERSECT(b_0,...,b_m;c'_0,...,c'_n;EPSILON);
      INTERSECT(b_0,...,b_m;c'_n,...,c'_2n;EPSILON);
    }
    else {
      Intersect line segments b_0b_m and c_0c_n;
    }
  }

其中delta^2(b_i)定义为b_{i+2} - 2*b_{i+1} + b_i