我有一个3D点的列表和一个中心,我想把它们按顺时针顺序排序,围绕给定的法线向量。这些点不是共面的,但是它们和中心被绑定到一个球的表面上,它们勾勒出一个多边形。法向量是从球体中心到排序中心中心的向量。我试过this comparison function,但是当两个点比π/2多时,它就失败了。

如何获得任意一组点的实际3D (逆时针)顺时针排序?
这不是https://stackoverflow.com/questions/31307712/sorting-3d-points-on-the-surface-of-a-sphere-in-clockwise-order的重复,因为这个问题是专门处理在角度比较中缺乏传递性的问题。
这不是https://stackoverflow.com/questions/14370636/sorting-a-list-of-3d-coplanar-points-to-be-clockwise-or-counterclockwise的重复,因为这个问题更多的是确定一个点是接近顺时针方向,还是接近于另一个点的逆时针方向,虽然这是一个比较关系,但它并没有给出一个定义良好的全序。
发布于 2017-12-23 05:13:04
正如你已经知道的,一个单点乘积本身不能工作,因为它是一个标量余弦,每个余弦值对应于单位圆的两个点。
解决这个问题的一种方法是在法线给出的平面上找到两个垂直的参考向量,然后取三重乘积。它们将是你可以用来排序的角度的正弦和余弦。所以你可以用atan2(y,x)来得到一个精确的角度,或者-如果速度是重要的-近似的atan2/(pi/4)斜率和反斜率。
要得到所需的两个向量,首先取最长的交叉积I x n、J x n和K x n,其中I、J、K是单位轴向量。叫这个矢量p。它必须在平面上,因为它垂直于n。(为了避免浮点精度问题,您花费的时间最长。)
现在计算q = n x p。这也在平面上,因为它垂直于n,但它也垂直于p。这正是我们需要的。
概括地说,p和q在任何平面上都是垂直向量,n是正常的。
如果c是中心,对于多边形中的每一点r,计算三重乘积t = n * ((r - c) x p)和u = n * ((r - c) x q)。那么atan2(u, t)或它的近似是一个排序度量。
Demo
为了证明这一点确实有效,包括atan2近似:
public class Sorter3d {
// Sorting key metric calculator.
static class Order {
final Vec n, pp, qp;
final Pt c;
Order(Vec n, Pt c) {
this.c = c;
this.n = n;
pp = n.cross(Vec.I).longer(n.cross(Vec.J)).longer(n.cross(Vec.K));
qp = n.cross(pp);
}
double getKey(Pt r) {
Vec rmc = r.minus(c);
return approxAtan2(n.dot(rmc.cross(pp)), n.dot(rmc.cross(qp)));
}
}
// Affine 3d vectors.
static class Vec {
static final Vec I = Vec.of(1, 0, 0);
static final Vec J = Vec.of(0, 1, 0);
static final Vec K = Vec.of(0, 0, 1);
final double x, y, z;
private Vec(double x, double y, double z) { this.x = x; this.y = y; this.z = z; }
static Vec of(double x, double y, double z) { return new Vec(x, y, z); }
Vec cross(Vec o) { return Vec.of(y * o.z - z * o.y, z * o.x - x * o.z, x * o.y - y * o.x); }
double dot(Vec o) { return x * o.x + y * o.y + z * o.z; }
double dot(Pt o) { return x * o.x + y * o.y + z * o.z; }
double len2() { return dot(this); }
double len() { return Math.sqrt(len2()); }
Vec scale(double s) { return Vec.of(x * s, y * s, z * s); }
Vec unit() { return scale(1.0 / len()); }
Vec longer(Vec o) { return len2() > o.len2() ? this : o; }
public String toString() { return String.format("[%.3f,%.3f,%.3f]", x, y, z); }
}
// Affine 3d points.
static class Pt {
static final Pt O = Pt.of(0, 0, 0);
final double x, y, z;
private Pt(double x, double y, double z) { this.x = x; this.y = y; this.z = z; }
static Pt of(double x, double y, double z) { return new Pt(x, y, z); }
Pt plus(Vec o) { return Pt.of(x + o.x, y + o.y, z + o.z); }
Vec minus(Pt o) { return Vec.of(x - o.x, y - o.y, z - o.z); }
public String toString() { return String.format("(%.3f,%.3f,%.3f)", x, y, z); }
}
// Return approximation of atan2(y,x) / (PI/2);
static double approxAtan2(double y, double x) {
int o = 0;
if (y < 0) { x = -x; y = -y; o |= 4; }
if (x <= 0) { double t = x; x = y; y = -t; o |= 2; }
if (x <= y) { double t = y - x; x += y; y = t; o |= 1; }
return o + y / x;
}
public static void main(String [] args) {
// Make some random points radially sorted about the Z axis.
int nPts = 17;
Pt [] pts = new Pt[nPts];
for (int i = 0; i < nPts; ++i) {
double r = 1.0 + 10 * Math.random();
double theta = i * (2 * Math.PI / nPts);
pts[i] = Pt.of(r * Math.cos(theta), r * Math.sin(theta), 40.0 * (1 - Math.random()));
}
// Pick arbitrary normal vector and center point.
// Rotate z-axis to normal and translate origin to center.
Vec normal = Vec.of(-42.0, 17.0, -91.0);
Vec cx = Vec.J.cross(normal).unit();
Vec cy = normal.cross(cx).unit();
Vec cz = normal.unit();
Vec rx = Vec.of(cx.x, cy.x, cz.x);
Vec ry = Vec.of(cx.y, cy.y, cz.y);
Vec rz = Vec.of(cx.z, cy.z, cz.z);
Pt center = Pt.of(11, 12, 13);
Vec ofs = center.minus(Pt.O);
Pt [] xPts = new Pt[nPts];
for (int i = 0; i < nPts; ++i) {
xPts[i] = Pt.of(rx.dot(pts[i]), ry.dot(pts[i]), rz.dot(pts[i])).plus(ofs);
}
// Check the sort keys returned by the sorter.
Order order = new Order(normal, center);
for (int i = 0; i < nPts; ++i) {
System.out.println(order.getKey(xPts[i]));
}
}
}这将打印一个有效的密钥顺序:
4.0
3.9924071330572093
3.982224060033384
3.9612544376696253
3.8080585081381275
0.03457371559793447
0.013026386180392412
0.006090856009723169
0.0018388671161891966
7.99632901621898
7.987892035846782
7.974282237149798
7.93316335979413
4.106158894193932
4.019755500146331
4.008967674404233
4.003810901304664发布于 2017-12-23 22:34:43
好的,我已经找到了我自己的解决方案,它只使用点和交叉积,不使用反三角形或平方根或任何东西。您可以在列表中选择第一个顶点v,并将其用作参考。然后将向量r = v - center与法线向量相交,得到半空间的分区向量p。如果这两个输入位于p的同一侧,那么您可以使用三元乘积,而不存在任何问题,因为它们之间的圆柱形角度小于π。虽然有一些边缘的情况需要注意,所以我想我应该分享一些伪代码。
let c be the center around which the counterclockwise sort is to be performed
let n be the normal vector
r := vertices[0] - c // use an arbitrary vector as the twelve o’clock reference
p := cross(r, c) // get the half-plane partition vector
// returns true if v1 is clockwise from v2 around c
function less(v1, v2):
u1 := v1 - c
u2 := v2 - c
h1 := dot(u1, p)
h2 := dot(u2, p)
if h2 ≤ 0 and h1 > 0:
return false
else if h1 ≤ 0 and h2 > 0:
return true
else if h1 = 0 and h2 = 0:
return dot(u1, r) > 0 and dot(u2, r) < 0
else:
return dot(cross(u1, u2), c) > 0
// h2 > 0 h2 = 0 h2 < 0
// ———————— ———————— ————————
// h1 > 0 | * v1 > v2 v1 > v2
// h1 = 0 | v1 < v2 † *
// h1 < 0 | v1 < v2 * *
// * means we can use the triple product because the (cylindrical)
// angle between u1 and u2 is less than π
// † means u1 and u2 are either 0 or π around from the zero reference
// in which case u1 < u2 only if dot(u1, r) > 0 and dot(u2, r) < 0发布于 2017-12-23 15:31:01
你把点投影在垂直于法线的平面上(与法线形成正交的框架)。然后在这个平面上,使用极坐标并按角度排序。无论如何,注意,角度的零点是任意的。
https://stackoverflow.com/questions/47949485
复制相似问题