按顺时针顺序对 3D 点列表进行排序

标签 sorting math 3d geometry

我有一个 3D 点列表和一个中心,我想围绕给定的法向量按(逆)时针顺序对它们进行排序。这些点不共面,但它们和中心被绑定(bind)到球体的表面,并且它们勾勒出一个多边形。法向量是从球体中心到分选中心中心的向量。我试过 this comparison function , 但当两点之间的距离大于 π/2 时,它会失败。

the colors of the vertices are the “order” the algorithm in the other answer gives. the white node is the center

如何获得任意点集的实际 3D(逆)时针顺序?

这不是 Sorting 3D points on the surface of a sphere in clockwise order 的副本因为这个问题专门是关于处理角度比较中缺乏传递性的问题。

这不是 Sorting a List of 3d coplanar points to be clockwise or counterclockwise 的副本因为这个问题更多地是关于确定一个点相对于另一个点是更接近顺时针还是逆时针,虽然它是一个比较关系,但它并没有给出明确定义的总排序。

最佳答案

如您所知,单个点积不能单独工作,因为它是标量余弦,余弦的每个值都对应单位圆的两个点。

因此,一种解决方法是在法线给定的平面中找到两个垂直引用向量,并与它们进行三重乘积。它们将是可用于排序的角度的正弦和余弦。因此,您可以使用 atan2(y,x) 获得精确的角度,或者 - 如果速度很重要 - 近似 atan2/(pi/4) 斜率和倒数坡度。

要获得所需的两个向量,首先取最长的叉积 I x nJ x nK x n 其中 I, J, K 为单位轴向量。将此向量称为 p。它必须位于平面内,因为它垂直于 n。 (您花费了最长的时间来避免浮点精度问题。)

现在计算q = n x p。这也位于平面内,因为它垂直于 n,但它也垂直于 p ...正是我们需要的。

回顾一下,pq 是任何平面中的垂直向量,其中n 是法线。

现在如果 c 是中心,对于多边形中的每个点 r,计算三重乘积 t = n * ((r - c) x p) u = n * ((r - c) x q)。那么atan2(u, t)或其近似值就是一个排序指标。

演示

只是为了证明这确实有效,包括 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

关于按顺时针顺序对 3D 点列表进行排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47949485/

相关文章:

arrays - arr.sort(key=lambda x : (x[0], -x[1])) 是什么意思?

algorithm - 在数组中找到时间最短的中位数

java - jme3 - UV 贴图错位在从 Blender 导出的模型上

html - 如何调整 3D CSS 立方体的大小

c++ - 3D 网格中的 block 附件

algorithm - 二叉搜索树与排序双向链表

java - 如何返回二维字符串

javascript - 使用 JavaScript 按字母顺序排序丹麦语?

python - Sympy Solve( ) 给出不正确的答案

java - 计算除以二的次数