Обнаружение самопересечения в замкнутых кривых Безье

Я создал "blob" форму, исправляя кубические кривые Безье вместе (снимок экрана ниже). Я хотел бы иметь возможность обнаружить ситуацию, когда кривая пересекла себя или другую кривую и задавалась вопросом, существует ли рекомендованный подход или известный алгоритм для этого?

Одна из моих идей заключалась в том, чтобы использовать FlatteningPathIterator для разложения фигуры на прямые сегменты линии, а затем определить, пересекается ли данный сегмент с другим, но мне было бы интересно, есть ли лучший подход (так как это будет имеют квадратичную производительность). Если я продолжу этот метод, существуют ли библиотечные функции в Java, чтобы определить, перекрываются ли два сегмента линии?

Спасибо.

Без перекрестных ссылок

Нет кроссовера http://www.freeimagehosting.net/uploads/7ad585414d.png

Cross-Over

Кроссовер http://www.freeimagehosting.net/uploads/823748f8bb.png

Ответ 1

Я действительно нашел рабочее решение, которое использует встроенные функции 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);
}
}

Ответ 2

Что вы можете сделать, это взять векторную функцию для кривая Безье:

alt text

И сравните различные кривые Безье, которые составляют вашу кривую в парах, чтобы увидеть, есть ли решение в [0,1]. Конечно, это помогло бы в случае, когда части, которые перекрываются, являются частью разных кривых. Это не поможет в случае, когда одна кривая пересекает себя...

EDIT:

Я цитировал функцию квадратичной кривой, так что это кубическая: alt text

И действительно сложно решить уравнение. В качестве альтернативы я предлагаю использовать более свободный метод. Что вы можете сделать, так это "разбить" каждую кривую на n точек и вычислить их положение, используя вышеприведенную функцию. Тогда для каждой из этих точек вы будете рассматривать диск произвольного радиуса (в зависимости от размеров кривых) и искать пересечения этих дисков. Вам нужно будет игнорировать пересечения последовательных дисков, так как пересечение происходит только потому, что они лежат слишком близко друг к другу на одном сегменте кривой.

Этот метод должен быть очень быстрым, но вы можете потерять точность, если вы выберете "неправильные" параметры (размер n и радиус), но вы можете экспериментировать.

Ответ 3

Я не уверен, что это помогает, но похоже на проблему в рендеринге полигонов, где у вас есть для каждой строки развертки Y массив пар значений (X, флаг), где линии пересекают эту строку сканирования.

Следуйте каждой кривой в форме и где она пересекает каждую строку сканирования Y, записывает (X, флаг), где флаг = 1, если он идет "на север" и флаг = -1, если идет "на юг".

Если на каждой строке сканирования вы рассматриваете пары в порядке X и сохраняете текущую сумму значений флага, тогда сумма между диапазоном двух значений Х будет положительной, если кривая будет по часовой стрелке и отрицательной, если кривая против часовой стрелки.

Если все пролеты равны +1 или все пролеты равны -1, кривая не пересекает себя.

Изменить: это занимает время, линейное по числу линий сканирования, пересекаемых фигурой. Затем результирующую структуру данных можно использовать для визуализации фигуры.

Ответ 4

Я думаю, вы можете получить приличное приближение

  • Используя FlatteningPathIterator, чтобы получить многоугольник, который аппроксимирует blob.
  • Разделение пути вокруг многоугольника на подпуты неубывания y (то есть нисходящие пути - представить рисунок полигона, используя только нисходящие штрихи карандаша).
  • Проходя вниз по дорожкам, сравнивая каждый сегмент линии только с отрезками линии, которые, по меньшей мере, перекрываются по размеру y.

Это довольно просто и позволяет избежать производительности O (n 2), о которой вы беспокоитесь. Для вашего среднего базового blob, как и у вас на иллюстрации, есть только два нисходящих пути.

Вы можете уменьшить количество сравнений, сохранив нисходящие пути, отсортированные по горизонтали, когда вы идете (a TreeSet, возможно).

Другим способом сравнения только сегментов линии, которые перекрываются в измерении y, является использование дерева интервалов.

Ответ 5

Здесь некоторый рекурсивный алгоритм из лекции Prof. Георг Умлауф

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.