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

tay*_*ift 1 sorting math 3d geometry

我有一个3D点和一个中心的列表,我想围绕一个给定的法向矢量按(逆时针)顺序对它们进行排序。这些点不是共面的,但是它们和中心绑定到球体的表面,并且它们勾勒出多边形。法线向量是从球心到分类中心的向量。我尝试了此比较功能,但是当两点?/2之间的距离大于彼此时,它就会失败。

顶点的颜色是另一个答案中算法给出的“顺序”。 白色节点为中心

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

这不是按顺时针方向对球体表面上的3D点进行排序的重复,因为此问题专门用于解决角度比较中缺少传递性的问题。

这不是将3d共面点列表按顺时针或逆时针排序的重复,因为该问题更多地是关于确定一个点是否与另一个点更接近顺时针或逆时针,尽管这是一个比较关系,但它不会给出定义明确的总排序。

Gen*_*ene 5

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

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

为了得到你所需要的两个向量,先取最长的产品I x nJ x nK x n在那里IJK是单位轴矢量。称这个向量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]));
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这将打印有效的键顺序:

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
Run Code Online (Sandbox Code Playgroud)