正如你已经发现的那样,单个点积不能独立工作,因为它是标量余弦,每个余弦值对应于单位圆的两个点。
因此,解决方案之一是在由法线给出的平面上找到两个垂直的参考向量,并使用它们进行三重乘积。它们将是您可以用于排序的角度的正弦和余弦。因此,您可以使用
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)
或其近似值是排序度量。
演示
只是为了展示这确实起作用,包括
atan2
的近似值:
public class Sorter3d {
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)));
}
}
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); }
}
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); }
}
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) {
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()));
}
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);
}
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
atan2(u, t)
可以避免或替换为更简单的函数吗?我不需要角度,只需要总排序。 - taylor swift