如何通过逆时针增加给定轴向量的角度来对点/向量数组进行排序?
例如:
如果0
是轴向量,我希望排序的数组的顺序为2, 3, 1
。
我有理由相信,使用交叉产品,自定义比较器和std::sort()
可以做到这一点。
答案 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;
}
};
答案 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)的点开始按列表中点的旋转顺序对列表进行排序,然后按逆时针方向旋转(如果在数学运算之前未加上负号,则为顺时针方向)。