Вычислить пересечение двух ребер

У меня есть большой граф ребер и вершин в 2D-пространстве. Большой график возвращается функцией, вычисленной в библиотеке C ++. Я читаю этот график и использую его для вычисления всех пересечений его ребер (сегментов линий). Я использую алгоритм развертки. У меня есть проблемы с обнаружением пересечения двух ребер. У меня есть 4 разных метода, в соответствии с которыми я проверяю, пересекаются ли два ребра, и если да, я вычисляю и сохраняю их пересечение:

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

  2. Тот, который вычисляет пересечение каждый раз и проверяет, находится ли найденное пересечение между конечными точками обоих сегментов.

  3. Код из этой ссылки реализован. хотя в C ++.

  4. Тот, который реализует первый метод, предложенный Джейсоном Коэном ín этот вопрос.

«Проблема сводится к следующему вопросу: пересекаются ли две прямые от A до B и от C до D? Тогда вы можете задать его четыре раза (между линией и каждой из четырех сторон прямоугольника).

Вот векторная математика для этого. Я предполагаю, что линия от A до B - это рассматриваемая линия, а линия от C до D - одна из линий прямоугольника. Я считаю, что Ax - это «координата x точки A», а Cy - «координата y точки C.» А «*» означает скалярное произведение, например:

A*B = Ax*Bx + Ay*By.
E = B-A = ( Bx-Ax, By-Ay )
F = D-C = ( Dx-Cx, Dy-Cy ) 
P = ( -Ey, Ex )
h = ( (A-C) * P ) / ( F * P )

Это число h является ключевым. Если h находится между 0 и 1, линии пересекаются, в противном случае - нет. Если F P равно нулю, вы, конечно, не можете произвести расчет, но в этом случае линии параллельны и, следовательно, пересекаются только в очевидных случаях. Точная точка пересечения - C + F h. Если h точно 0 или 1, линии соприкасаются в конечной точке. Можете считать это «перекрестком» или нет, как считаете нужным ».

Для данных, которые я создал (небольшие данные с двойными значениями), я получил хорошие результаты со всеми 4 реализованными методами. Когда я использую любой из этих методов, реализованных на C ++, для данных из большого графа, я получаю каждый раз разные результаты и не очень хорошие:

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

  2. Я всегда получаю 0 пересечений, несмотря ни на что.

  3. Я получаю намного больше пересечений, чем в 1.

  4. Для некоторых примеров я получаю точки, которых нет на графике (то есть даже не пересечение). Но для некоторых примеров я получаю точки пересечения плюс некоторые другие точки.

Понятия не имею, в чем может быть проблема. Любые предложения или любые другие идеи о том, как вычислить пересечение и проверить его, более того, скорее, чем приветствуются. спасибо, мадалина


person madalina    schedule 01.04.2009    source источник


Ответы (6)


Что вам нужно, так это модульные тесты для ваших 4 методов и их тщательное тестирование. В частности, в случае пересечений отрезков и линий существует множество конечных случаев, таких как параллельные уклоны, совпадающие конечные точки, полное или частичное перекрытие, в дополнение ко всем обычным проблемам с допусками (например, что именно делает " равный наклон "значит?).

Не разбивая вещи на более мелкие, более тестируемые единицы, вам будет сложно сузить его.

person Jeff Kotula    schedule 01.04.2009

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

Существует библиотека геометрии с открытым исходным кодом GEOS (http://trac.osgeo.org/geos/), что может быть полезно для проверки ваших данных. В GEOS также есть алгоритмы для выполнения округления с привязкой к целочисленной сетке и исключения общих битов, чтобы помочь вам определить, сталкиваетесь ли вы с проблемой устойчивости. Также следует отметить, как GEOS вычисляет пересечение с использованием дуальности точка-линия в однородном пространстве (что я не могу точно сказать, математически эквивалентна описываемая вами техника проекции скалярного произведения).

В стороне, мое любимое решение для вычисления пересечений в графе ребер - это использование прямой линии в сочетании с монотонной цепочкой ребер. Когда вы используете монотонные цепи, вы можете исключить множество тестов на пересечение ребер, поскольку цепи никогда не пересекаются сами по себе. Это то, что делает GEOS.

person codekaizen    schedule 01.04.2009

Зачем изобретать колесо?

Если вы умеете полагаться на Boost, я бы посмотрел библиотеку GTL в песочница. Инструкции по загрузке см. В wiki (инструкции прямо на лицевой стороне страница).

Библиотека GTL подходит для любой реализации вашей структуры данных (т.е. она разработана в духе STL, где структуры данных и алгоритмы ортогональны). Код одновременно быстрый и правильный. Проверьте это, если сможете.

person Trey Jackson    schedule 02.04.2009

Вы можете попробовать Boost.Geometry (также известную как Generic Geometry Library).

person mloskot    schedule 25.01.2010

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

Это код для 4-го метода (который пока дает мне лучшие результаты, но не все пересечения, но, по крайней мере, некоторые хорошие пересечения по сравнению с другими):

//4 method
double* QSweep::findIntersection(edge_t edge1,edge_t edge2) {  
    point_t p1=myPoints_[edge1[0]];
    point_t p2=myPoints_[edge1[1]];
    point_t p3=myPoints_[edge2[0]];
    point_t p4=myPoints_[edge2[1]];

    double xD1,yD1,xD2,yD2,xD3,yD3,xP,yP,h,denom;
    double* pt=new double[3];

    // calculate differences  
    xD1=p2[0]-p1[0];  
    xD2=p4[0]-p3[0];  
    yD1=p2[1]-p1[1];  
    yD2=p4[1]-p3[1];  
    xD3=p1[0]-p3[0];  
    yD3=p1[1]-p3[1];    

    xP=-yD1;
    yP=xD1;
    denom=xD2*(-yD1)+yD2*xD1;
    if (denom==0) {
        return NULL;
    }
    else{
    h=(xD3*(-yD1)+yD3*xD1)/denom;
    }
    std::cout<<"h is"<<h<<endl;
    if ((h>0)&&(h<1)){
        pt[0]=p3[0]+xD2*h;  
        pt[1]=p3[1]+yD2*h;
        pt[2]=0.00;
    }
    else{
        return NULL;
    }

    // return the valid intersection  
    return pt;  
}

void QSweep:: intersection(){
    nbIntersections=0;
    double* vector;
    for (int i=2;i<myEdges_.size()-1;i++){
        vector=findIntersection(myEdges_[i-1],myEdges_[i]);
        if (vector!=NULL){
                nbIntersections++;
                myIntersections.push_back(vector);
                swap(myEdges_[i-1], myEdges_[i]);
        }
    }
}

Функция пересечения всегда одна и та же, поэтому я просто нахожу функцию findIntersection.

Для метода 1 и 2 я использую следующую версию функции findIntersection:

double* QSweep::computeIntersection(double m1, double b1, double m2, double b2){
    double* v=new double[3];

    v[0]= (b2-b1)/(m1-m2);
    v[1]= (m1*b2-m2*b1)/(m1-m2);
    v[2]=0.00;
    return v;
    }

    double* QSweep::findIntersection(edge_t edge1, edge_t edge2){


    //equation for the support line of the first edge

    double a=myPoints_[edge1[0]][0];
    double b=myPoints_[edge1[0]][1];
    double c=myPoints_[edge1[1]][0];
    double d=myPoints_[edge1[1]][1];
    double m1=getSlope(a,b,c,d);
    double b1=getYIntercept(a,b,c,d);

    double x=myPoints_[edge2[0]][0];
    double y=myPoints_[edge2[0]][1];
    double s=myPoints_[edge2[1]][0];
    double t=myPoints_[edge2[1]][1];
    double m2=getSlope(x,y,s,t);
    double b2=getYIntercept(x,y,s,t);
    double* vector;

    double line2InLine1=evalEqnLineD(myPoints_[edge2[0]],edge1);
    double line2InLine1New=evalEqnLineD(myPoints_[edge2[1]],edge1);
    double line1InLine2=evalEqnLineD(myPoints_[edge1[0]],edge2);
    double line1InLine2New=evalEqnLineD(myPoints_[edge1[1]],edge2);

    if (m1==m2){
        return NULL;
    }
    else{

            if ((line1InLine2*line1InLine2New)<0 &&   (line2InLine1*line2InLine1New)<0){
            vector=computeIntersection(m1,b1,m2,b2);

            if ((vector[0]>a && vector[0]<c)&&(vector[0]>x && vector[0]<s)){
                return vector;
            }
            else{
                return NULL;
            }
        }
        else{
            return NULL;
        }
    }
    return vector;
}

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

person madalina    schedule 02.04.2009

Вы только что сказали: алгоритм развертки ... Я использовал усиленный алгоритм Майерса 1985 года, в то время этот алгоритм был лучшим.

Что я сделал: работал с целыми числами, т.е. фиксированными точками. - числа, чтобы иметь дело с проблемами точности.

Что касается алгоритма пересечения двух сегментов: первые очевидные тесты для диапазонов и параллельности сегментов. Затем я вычислил ожидаемую точку пересечения в двойном размере и угол между двумя сегментами. Всякий раз, когда угол между двумя сегментами был «маленьким», я вычислял расстояние от точки пересечения, где расстояние между двумя линейными сегментами становится больше единицы в моем целочисленном пространстве. Затем я разделил каждый из двух сегментов на 3, например> ------- ‹с общей частью, так сказать, расширив точку пересечения до отдельного сегмента, и удалил первые два сегмента. Я не стал беспокоиться, если лежащие ниже полигоны станут вогнутыми.

Поскольку сегменты были вычислены для вычисления графа разбиения многоугольников, граф разбиения имел вместо двух почти параллельных сегментов всего 3 сегмента с более правильными углами.

Повышение также содержало распределительную сортировку слиянием точек событий (в настоящее время: такой алгоритм сильно распараллеливается !!!), тем самым уменьшая сложность сортировки от ожидаемого O (n log n) до ожидаемого O (n), но O (n log n) худший случай. Я настоятельно рекомендую пересмотреть алгоритм развертки (однопоточный) к некоторым методам «разделяй и властвуй», распараллелив его.

person Attila Törcsvári    schedule 26.06.2017