Подтвердить что ты не робот

Сортировка точек по углу от заданной оси?

Как я могу отсортировать массив точек/векторов, увеличивая угол против часовой стрелки от данного вектора оси?

Например:

example configuration

Если 0 - вектор оси, я бы ожидал, что отсортированный массив будет в порядке 2, 3, 1.

Я уверен, что это можно сделать с помощью перекрестных продуктов, специализированного компаратора и std::sort().

4b9b3361

Ответ 1

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

Это будет намного быстрее, чем что-либо, связанное с триггером. Там даже не нужно сначала нормализовать.

Здесь компаратор:

class angle_sort
{
    point m_origin;
    point m_dreference;

    // z-coordinate of cross-product, aka determinant
    static double xp(point a, point b) { return a.x * b.y - a.y * b.x; }
public:
    angle_sort(const point origin, const point reference) : m_origin(origin), m_dreference(reference - origin) {}
    bool operator()(const point a, const point b) const
    {
        const point da = a - m_origin, db = b - m_origin;
        const double detb = xp(m_dreference, db);

        // nothing is less than zero degrees
        if (detb == 0 && db.x * m_dreference.x + db.y * m_dreference.y >= 0) return false;

        const double deta = xp(m_dreference, da);

        // zero degrees is less than anything else
        if (deta == 0 && da.x * m_dreference.x + da.y * m_dreference.y >= 0) return true;

        if (deta * detb >= 0) {
            // both on same side of reference, compare to each other
            return xp(da, db) > 0;
        }

        // vectors "less than" zero degrees are actually large, near 2 pi
        return deta > 0;
    }
};

Демо: http://ideone.com/YjmaN

Ответ 2

Самый простой, но, возможно, не оптимальный способ - сдвиг декартовых координат относительно центральной точки, а затем преобразовать их в полярные координаты, Затем просто вычтите угол "стартового вектора" по модулю 360 и, наконец, отсортируйте по углу.

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

Ответ 3

#include <iostream>
#include <cmath>
#include <algorithm>

using namespace std;

struct Point {
    static double base_angle;
    static void set_base_angle(double angle){
        base_angle = angle;
    }
    double x;
    double y;
    Point(double x, double y):x(x),y(y){}
    double Angle(Point o = Point(0.0, 0.0)){
        double dx = x - o.x;
        double dy = y - o.y;
        double r = sqrt(dx * dx + dy * dy);
        double angle = atan2(dy , dx);
        angle -= base_angle;
        if(angle < 0) angle += M_PI * 2;
        return angle;
    }
};
double Point::base_angle = 0;

ostream& operator<<(ostream& os, Point& p){
    return os << "Point(" << p.x << "," << p.y << ")";
}

bool comp(Point a, Point b){
    return a.Angle() < b.Angle();
}

int main(){
    Point p[] = { Point(-4., -4.), Point(-6., 3.), Point(2., -4.), Point(1., 5.) };
    Point::set_base_angle(p[0].Angle());
    sort(p, p + 4, comp);
    Point::set_base_angle(0.0);
    for(int i = 0;i< 4;++i){
        cout << p[i] << " angle:" << p[i].Angle() << endl;
    }
}

DEMO

Point(-4,-4) angle:3.92699
Point(2,-4) angle:5.17604
Point(1,5) angle:1.3734
Point(-6,3) angle:2.67795

Ответ 4

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

struct sorter { 
    operator()(point a, point b) const {  
        if (a.y > 0) { //a between 0 and 180
            if (b.y < 0)  //b between 180 and 360
                return false;
            return a.x < b.x; 
        } else { // a between 180 and 360
            if (b.y > 0) //b between 0 and 180
                return true;
            return a.x > b.x;
        }
    }
    //for comparison you don't need exact angles, simply relative.
}

Это быстро отсортирует их с 0- > 360 degress. Затем вы найдете свой вектор 0 (в позиции N), а std::rotate результаты оставят N элементов. (Спасибо TomSirgedas!)

Ответ 5

Сначала вы должны нормализовать каждый вектор, поэтому каждая точка находится в формате (cos (t_n), sin (t_n)). Затем вычисляем cos и sin углы между каждой точкой и точкой отсчета. Конечно:

cos(t_n-t_0)=cos(t_n)cos(t_0)+sin(t_n)sin(t_0)  (this is equivalent to dot product)
sin(t_n-t_0)=sin(t_n)cos(t_0)-cos(t_n)sin(t_0)

Только на основе обоих значений, вы можете определить точные углы (-pi пи) между точками и точкой отсчета. Если использовать только точечный продукт, то по часовой стрелке и против часовой стрелки одинакового угла имеют одинаковые значения. Вы определяете угол, сортируете их.

Ответ 6

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

std::sort(vectors.begin(), vectors.end(), VectorComp(centerPoint));

Ниже приведен код для сравнения

struct VectorComp : std::binary_function<sf::Vector2f, sf::Vector2f, bool>
{

    sf::Vector2f M;
    IntersectComp(sf::Vector2f v) : M(v) {}

    bool operator() ( sf::Vector2f o1,  sf::Vector2f o2)
    {
        float ang1     = atan( ((o1.y - M.y)/(o1.x - M.x) ) * M_PI / 180);
        float ang2     = atan( (o2.y - M.y)/(o2.x - M.x) * M_PI / 180);
        if(ang1 < ang2) return true;
        else if (ang1 > ang2) return false;
        return true;
    }
};

Он использует sfml-библиотеку, но вы можете переключить любой векторный/точечный класс вместо sf:: Vector2f. M будет центральной точкой. Он отлично работает, если вы хотите каким-то образом нарисовать поклонник треугольника.

Ответ 7

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

Он основан на делении плоскости на две половинки, одну от положительной оси ref (включительно) до отрицательной оси рефлекса (исключая), а другая - ее дополнение.

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

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

int compareAngles(Point const & A, Point const & B, Point const & ref = Point(0,0)) {
  typedef decltype(Point::x) T;  // for generality. this would not appear in real code.
  const T sinA = A.y - ref.y; // |A-ref|.sin(angle between A and positive ref-axis)
  const T sinB = B.y - ref.y; // |B-ref|.sin(angle between B and positive ref-axis)
  const T cosA = A.x - ref.x; // |A-ref|.cos(angle between A and positive ref-axis)
  const T cosB = B.x - ref.x; // |B-ref|.cos(angle between B and positive ref-axis)

  bool hA = ( (sinA < 0) || ((sinA == 0) && (cosA < 0)) ); // 0 for [0,180). 1 for [180,360).
  bool hB = ( (sinB < 0) || ((sinB == 0) && (cosB < 0)) ); // 0 for [0,180). 1 for [180,360).

  if (hA == hB) {
    // |A-ref|.|B-ref|.sin(angle going from (B-ref) to (A-ref))
    T sinBA = sinA * cosB - sinB * cosA;
    // if T is int, or return value is changed to T, it can be just "return sinBA;"
    return ((sinBA > 0) ? 1 : ((sinBA < 0) ? (-1) : 0));
  }
  return (hA - hB);
}

Ответ 8

Если S - это массив PointF, а mid - это PointF в центре:

S = S.OrderBy(s => -Math.Atan2((s.Y - mid.Y), (s.X - mid.X))).ToArray();

отсортирует список в порядке поворота вокруг середины, начиная с точки, ближайшей к (-inf, 0), и перейдет по часовой стрелке (по часовой стрелке, если вы опустите отрицательный знак перед математикой).