从给定轴按角度排序点?

时间:2011-10-14 22:29:08

标签: c++ algorithm math geometry vector-graphics

如何通过逆时针增加给定轴向量的角度来对点/向量数组进行排序?

例如:

example configuration

如果0是轴向量,我希望排序的数组的顺序为2, 3, 1

我有理由相信,使用交叉产品,自定义比较器和std::sort()可以做到这一点。

8 个答案:

答案 0 :(得分:12)

是的,您可以使用基于跨产品的自定义比较器来完成此操作。唯一的问题是一个天真的比较器不具备传递性。因此需要额外的步骤,以防止参考的任何一侧的角度被认为是接近的。

这比涉及trig的任何事情都要快得多。甚至没有必要先进行标准化。

这是比较器:

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

答案 1 :(得分:6)

最简单,但可能不是最佳方法是将笛卡尔坐标相对于中心点移动,然后convert them to polar coordinates。然后只需减去“起始矢量”模360的角度,最后按角度排序。

或者,你可以制作一个自定义比较器来处理所有可能的斜率和配置,但我认为极坐标更加透明。

答案 2 :(得分:2)

#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;
    }
}

<强>样本

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

答案 3 :(得分:1)

假设它们的长度相同并且具有相同的原点,您可以按

排序
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度降低。然后你找到你的向量0(在位置N),std::rotate结果留下了N个元素。 (谢谢TomSirgedas!)

答案 4 :(得分:0)

首先应对每个向量进行标准化,因此每个点都采用(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到pi)。如果只使用点积,则相同角度的顺时针和逆时针具有相同的值。一个你确定角度,对它们进行排序。

答案 5 :(得分:0)

这是我如何解决这个问题的一个例子。它转换为极性以获得角度,然后用于比较它们。您应该可以在类似的排序函数中使用它:

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库但你可以切换任何vector / point类而不是sf :: Vector2f。 M将是中心点。如果你想要绘制某种类型的三角扇,它会很有用。

答案 6 :(得分:0)

我知道这个问题很老了,接受的答案帮助我达到了这个目的,但我认为我有一个更优雅的解决方案,它也涵盖了相等性(因此对于lowerThan返回-1,对于equals返回0,对于greaterThan返回1) )。

它基于平面划分为两半,一个从正ref轴(包括)到负ref轴(不包括),另一个是它的补充。

在每一半内部,可以通过右手规则(交叉产品符号)进行比较,或者换句话说 - 两个向量之间的角度正弦的符号。 如果2点来自不同的一半,则比较是微不足道的,并且在两半之间完成。

对于一个充分均匀的分布,除了用ref做的4次减法之外,这个测试应该平均进行4次比较,1次减法和1次乘法,在我看来应该是预先计算的。

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);
}

答案 7 :(得分:0)

如果S是PointF的数组,而mid是中心的PointF:

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

将从最接近(-inf,0)的点开始按列表中点的旋转顺序对列表进行排序,然后按逆时针方向旋转(如果在数学运算之前未加上负号,则为顺时针方向)。