如何在更高维度的超球面上均匀分布点?

时间:2019-07-20 08:55:10

标签: math pseudocode

我对在尺寸大于等于3的球体表面上均匀分布N个点感兴趣。

更具体地说:

  • 给出点N和维数D(其中D> 1,N> 1)
  • 每个点到原点的距离必须为1
  • 两点之间的最小距离应尽可能大
  • 每个点到它最近的邻居的距离不必在每个点上都相同(实际上,除非点的数量形成柏拉图式实体的顶点,或者如果N <= D)。

我对以下内容不感兴趣:

  • 在超球面上创建均匀的随机分布,因为我希望任意两点之间的最小距离尽可能大而不是随机分布。
  • 粒子排斥模拟类型的方法,因为它们难以实现并且要花费很大的时间才能运行较大的N(理想情况下,该方法应是确定性的,且应为O(n))。

一种满足这些条件的方法称为斐波那契晶格,但我只能在2d和3d中找到针对该条件的代码实现。

斐波纳契晶格(也称为斐波纳契螺旋)背后的方法是生成一维线,该线围绕球体表面成螺旋形,以使该线所覆盖的表面积在每一转时大致相同。然后,您可以丢弃N个均匀分布在螺旋上的点,它们将大致均匀地分布在球体的表面上。

this answer中,有一个针对3个维度的python实现,会生成以下内容:

enter image description here

我想知道斐波那契螺旋是否可以扩展到大于3的尺寸,并在数学堆栈交换中发布了一个问题。令我惊讶的是,我收到two amazing answers,据我所知(因为我不完全理解所显示的数学),表明确实有可能将此方法扩展到N维。

不幸的是,我对所显示的数学知识还不够了解,无法将任何一个答案都转换为(伪)代码。我是一位经验丰富的计算机程序员,但我的数学背景仅此而已。

我将复制我认为是以下答案之一中最重要的部分(不幸的是,SO不支持mathjax,因此我必须复制为图像)

enter image description here

我遇到的上述困难:

  • 如何解析用于ψn的反函数?
  • 给出的示例是d = 3的。如何生成任意d的公式?

在座的任何人都可以理解所涉及的数学知识,从而能够朝着链接斐波那契晶格问题的任一答案的伪代码实现取得进展?我知道完整的实施可能很困难,所以我对部分实施感到满意,该实施可以使我足够自己完成其余的工作。

为了简化起见,我已经编写了一个函数,该函数将N个维度的球面坐标转换为笛卡尔坐标,因此该实现可以输出任意一个,因为我可以轻松转换。

此外,我看到一个答案为每个附加维使用下一个质数。我可以轻松地编写一个输出每个连续素数的函数,因此可以假定它已经实现。

未能在N维上实现斐波那契晶格,我很乐意接受满足上述约束的另一种方法。

5 个答案:

答案 0 :(得分:3)

非常有趣的问题。我想将其实现到mine 4D rendering engine中,因为我很好奇它的外观,但是我太懒惰且无能,无法从数学方面处理ND超验问题。

相反,我针对此问题提出了不同的解决方案。它不是 Fibonaci Latice !!! ,而是将hypersphere or n-sphere的参数方程式扩展为 hyperspiral ,然后仅拟合螺旋参数,以使点更多或等距的距离较小。

我知道这听起来很可怕,但并不是那么难,并且在解决了一些愚蠢的错别字复制/粘贴错误之后,结果对我来说(最终:)正确)

主要思想是对超球面使用n维参数方程式,以从角度和半径计算其表面点。在这里实现:

请参见 [edit2] 。现在问题可以归结为两个主要问题:

  1. 计算螺钉数量

    因此,如果我们希望我们的点是等距的,那么它们必须等距离地躺在螺旋路径上(请参见项目符号#2 ),但螺钉本身之间的距离也应相同。为此,我们可以利用超球面的几何特性。让我们从2D开始:

    2D spiral

    因此只需screws = r/d。点数也可以推断为points = area/d^2 = PI*r^2/d^2

    所以我们可以简单地将2D螺旋写为:

    t = <0.0,1.0>
    a = 2.0*M_PI*screws*t;
    x = r*t*cos(a);
    y = r*t*sin(a);
    

    为简单起见,我们可以假设r=1.0如此d=d/r(稍后再缩放点)。然后展开(每个维度仅添加angle参数)如下所示:

    2D:

    screws=1.0/d;          // radius/d
    points=M_PI/(d*d);     // surface_area/d^2
    a = 2.0*M_PI*t*screws;
    x = t*cos(a);
    y = t*sin(a);
    

    3D:

    screws=M_PI/d;         // half_circumference/d
    points=4.0*M_PI/(d*d); // surface_area/d^2
    a=    M_PI*t;
    b=2.0*M_PI*t*screws;
    x=cos(a)       ;
    y=sin(a)*cos(b);
    z=sin(a)*sin(b);
    

    4D:

    screws = M_PI/d;
    points = 3.0*M_PI*M_PI*M_PI/(4.0*d*d*d);
    a=    M_PI*t;
    b=    M_PI*t*screws;
    c=2.0*M_PI*t*screws*screws;
    x=cos(a)              ;
    y=sin(a)*cos(b)       ;
    z=sin(a)*sin(b)*cos(c);
    w=sin(a)*sin(b)*sin(c);
    

    现在要提防4D的问题只是我的假设。我凭经验发现它们与constant/d^3有关,但不完全相关。每个角度的螺丝都不同。我的假设是screws^i之外没有其他尺度,但可能需要不断调整(因为对我来说结果看起来不错,所以不对所得的点云进行分析)

    现在我们可以从单个参数t=<0.0,1.0>生成螺旋上的任何点。

    请注意,如果您逆转方程式,那么d=f(points)可以将点作为输入值,但要注意其近似的点数不准确!

  2. 在螺旋上生成台阶,使点等距

    这是我跳过代数混乱而使用拟合代替的部分。我只是简单地对搜索量t进行二进制搜索,因此所得点与上一个点d遥远。因此,只需生成点t=0,然后在估计位置附近进行二进制搜索t,直到d距起点为止。然后重复此操作,直到t<=1.0 ...

    您可以使用二进制搜索或其他方式。我知道它的速度不及O(1)代数方法,但不需要为每个维度派生东西。看起来10次迭代就可以拟合,因此也不慢。

这里是我的4D引擎 C ++ / GL / VCL 的实现:

void ND_mesh::set_HyperSpiral(int N,double r,double d)
    {
    int i,j;
    reset(N);
    d/=r;   // unit hyper-sphere
    double dd=d*d;  // d^2
    if (n==2)
        {
        // r=1,d=!,screws=?
        // S = PI*r^2
        // screws = r/d
        // points = S/d^2
        int i0,i;
        double a,da,t,dt,dtt;
        double x,y,x0,y0;
        double screws=1.0/d;
        double points=M_PI/(d*d);
        dbg=points;
        da=2.0*M_PI*screws;
        x0=0.0; pnt.add(x0);
        y0=0.0; pnt.add(y0);
        dt=0.1*(1.0/points);
        for (t=0.0,i0=0,i=1;;i0=i,i++)
            {
            for (dtt=dt,j=0;j<10;j++,dtt*=0.5)
                {
                t+=dtt;
                a=da*t;
                x=(t*cos(a))-x0; x*=x;
                y=(t*sin(a))-y0; y*=y;
                if ((!j)&&(x+y<dd)){ j--; t-=dtt; dtt*=4.0; continue; }
                if (x+y>dd) t-=dtt;
                }
            if (t>1.0) break;
            a=da*t;
            x0=t*cos(a); pnt.add(x0);
            y0=t*sin(a); pnt.add(y0);
            as2(i0,i);
            }
        }
    if (n==3)
        {
        // r=1,d=!,screws=?
        // S = 4*PI*r^2
        // screws = 2*PI*r/(2*d)
        // points = S/d^2
        int i0,i;
        double a,b,da,db,t,dt,dtt;
        double x,y,z,x0,y0,z0;
        double screws=M_PI/d;
        double points=4.0*M_PI/(d*d);
        dbg=points;
        da=    M_PI;
        db=2.0*M_PI*screws;
        x0=1.0; pnt.add(x0);
        y0=0.0; pnt.add(y0);
        z0=0.0; pnt.add(z0);
        dt=0.1*(1.0/points);
        for (t=0.0,i0=0,i=1;;i0=i,i++)
            {
            for (dtt=dt,j=0;j<10;j++,dtt*=0.5)
                {
                t+=dtt;
                a=da*t;
                b=db*t;
                x=cos(a)       -x0; x*=x;
                y=sin(a)*cos(b)-y0; y*=y;
                z=sin(a)*sin(b)-z0; z*=z;
                if ((!j)&&(x+y+z<dd)){ j--; t-=dtt; dtt*=4.0; continue; }
                if (x+y+z>dd) t-=dtt;
                }
            if (t>1.0) break;
            a=da*t;
            b=db*t;
            x0=cos(a)       ; pnt.add(x0);
            y0=sin(a)*cos(b); pnt.add(y0);
            z0=sin(a)*sin(b); pnt.add(z0);
            as2(i0,i);
            }
        }
    if (n==4)
        {
        // r=1,d=!,screws=?
        // S = 2*PI^2*r^3
        // screws = 2*PI*r/(2*d)
        // points = 3*PI^3/(4*d^3);
        int i0,i;
        double a,b,c,da,db,dc,t,dt,dtt;
        double x,y,z,w,x0,y0,z0,w0;
        double screws = M_PI/d;
        double points=3.0*M_PI*M_PI*M_PI/(4.0*d*d*d);
        dbg=points;
        da=    M_PI;
        db=    M_PI*screws;
        dc=2.0*M_PI*screws*screws;
        x0=1.0; pnt.add(x0);
        y0=0.0; pnt.add(y0);
        z0=0.0; pnt.add(z0);
        w0=0.0; pnt.add(w0);
        dt=0.1*(1.0/points);
        for (t=0.0,i0=0,i=1;;i0=i,i++)
            {
            for (dtt=dt,j=0;j<10;j++,dtt*=0.5)
                {
                t+=dtt;
                a=da*t;
                b=db*t;
                c=dc*t;
                x=cos(a)              -x0; x*=x;
                y=sin(a)*cos(b)       -y0; y*=y;
                z=sin(a)*sin(b)*cos(c)-z0; z*=z;
                w=sin(a)*sin(b)*sin(c)-w0; w*=w;
                if ((!j)&&(x+y+z+w<dd)){ j--; t-=dtt; dtt*=4.0; continue; }
                if (x+y+z+w>dd) t-=dtt;
                } dt=dtt;
            if (t>1.0) break;
            a=da*t;
            b=db*t;
            c=dc*t;
            x0=cos(a)              ; pnt.add(x0);
            y0=sin(a)*cos(b)       ; pnt.add(y0);
            z0=sin(a)*sin(b)*cos(c); pnt.add(z0);
            w0=sin(a)*sin(b)*sin(c); pnt.add(w0);
            as2(i0,i);
            }
        }

    for (i=0;i<pnt.num;i++) pnt.dat[i]*=r;
    for (i=0;i<s1.num;i++) s1.dat[i]*=n;
    for (i=0;i<s2.num;i++) s2.dat[i]*=n;
    for (i=0;i<s3.num;i++) s3.dat[i]*=n;
    for (i=0;i<s4.num;i++) s4.dat[i]*=n;
    }

在设置n=N的维数的情况下,r是半径,而d是点之间的期望距离。我使用了许多未在此处声明的内容,但重要的是pnt[]列出了对象的点列表,而as2(i0,i1)将索引i0,i1处的点添加到了网格。

这里有几个截图...

3D透视:

3D perspective

4D视角:

4D perspective

具有超平面w=0.0的4D横截面:

4D cross-section w=0.0

并且具有更多点和更大半径的情况:

4D cross-section w=0.0 HQ

形状随着动画的旋转而变化...

[Edit1]更多代码/信息

这是我的引擎网格类的样子:

//---------------------------------------------------------------------------
//--- ND Mesh: ver 1.001 ----------------------------------------------------
//---------------------------------------------------------------------------
#ifndef _ND_mesh_h
#define _ND_mesh_h
//---------------------------------------------------------------------------
#include "list.h"     // my dynamic list you can use std::vector<> instead
#include "nd_reper.h" // this is just 5x5 transform matrix
//---------------------------------------------------------------------------
enum _render_enum
    {
    _render_Wireframe=0,
    _render_Polygon,
    _render_enums
    };
const AnsiString _render_txt[]=
    {
    "Wireframe",
    "Polygon"
    };
enum _view_enum
    {
    _view_Orthographic=0,
    _view_Perspective,
    _view_CrossSection,
    _view_enums
    };
const AnsiString _view_txt[]=
    {
    "Orthographic",
    "Perspective",
    "Cross section"
    };
struct dim_reduction
    {
    int view;               // _view_enum
    double coordinate;      // cross section hyperplane coordinate or camera focal point looking in W+ direction
    double focal_length;
    dim_reduction()     { view=_view_Perspective; coordinate=-3.5; focal_length=2.0; }
    dim_reduction(dim_reduction& a) { *this=a; }
    ~dim_reduction()    {}
    dim_reduction* operator = (const dim_reduction *a) { *this=*a; return this; }
    //dim_reduction* operator = (const dim_reduction &a) { ...copy... return this; }
    };
//---------------------------------------------------------------------------
class ND_mesh
    {
public:
    int n;              // dimensions

    List<double> pnt;   // ND points        (x0,x1,x2,x3,...x(n-1))
    List<int>    s1;    // ND points        (i0)
    List<int>    s2;    // ND wireframe     (i0,i1)
    List<int>    s3;    // ND triangles     (i0,i1,i2,)
    List<int>    s4;    // ND tetrahedrons  (i0,i1,i2,i3)
    DWORD col;          // object color 0x00BBGGRR
    int   dbg;          // debug/test variable

    ND_mesh()   { reset(0); }
    ND_mesh(ND_mesh& a) { *this=a; }
    ~ND_mesh()  {}
    ND_mesh* operator = (const ND_mesh *a) { *this=*a; return this; }
    //ND_mesh* operator = (const ND_mesh &a) { ...copy... return this; }

    // add simplex
    void as1(int a0)                     { s1.add(a0); }
    void as2(int a0,int a1)              { s2.add(a0); s2.add(a1); }
    void as3(int a0,int a1,int a2)       { s3.add(a0); s3.add(a1); s3.add(a2); }
    void as4(int a0,int a1,int a2,int a3){ s4.add(a0); s4.add(a1); s4.add(a2); s4.add(a3); }
    // init ND mesh
    void reset(int N);
    void set_HyperTetrahedron(int N,double a);              // dimensions, side
    void set_HyperCube       (int N,double a);              // dimensions, side
    void set_HyperSphere     (int N,double r,int points);   // dimensions, radius, points per axis
    void set_HyperSpiral     (int N,double r,double d);     // dimensions, radius, distance between points
    // render
    void glDraw(ND_reper &rep,dim_reduction *cfg,int render);   // render mesh
    };
//---------------------------------------------------------------------------
#define _cube(a0,a1,a2,a3,a4,a5,a6,a7) { as4(a1,a2,a4,a7); as4(a0,a1,a2,a4); as4(a2,a4,a6,a7); as4(a1,a2,a3,a7); as4(a1,a4,a5,a7); }
//---------------------------------------------------------------------------
void ND_mesh::reset(int N)
    {
    dbg=0;
    if (N>=0) n=N;
    pnt.num=0;
    s1.num=0;
    s2.num=0;
    s3.num=0;
    s4.num=0;
    col=0x00AAAAAA;
    }
//---------------------------------------------------------------------------
void ND_mesh::set_HyperSpiral(int N,double r,double d)
    {
    int i,j;
    reset(N);
    d/=r;   // unit hyper-sphere
    double dd=d*d;  // d^2
    if (n==2)
        {
        // r=1,d=!,screws=?
        // S = PI*r^2
        // screws = r/d
        // points = S/d^2
        int i0,i;
        double a,da,t,dt,dtt;
        double x,y,x0,y0;
        double screws=1.0/d;
        double points=M_PI/(d*d);
        dbg=points;
        da=2.0*M_PI*screws;
        x0=0.0; pnt.add(x0);
        y0=0.0; pnt.add(y0);
        dt=0.1*(1.0/points);
        for (t=0.0,i0=0,i=1;;i0=i,i++)
            {
            for (dtt=dt,j=0;j<10;j++,dtt*=0.5)
                {
                t+=dtt;
                a=da*t;
                x=(t*cos(a))-x0; x*=x;
                y=(t*sin(a))-y0; y*=y;
                if ((!j)&&(x+y<dd)){ j--; t-=dtt; dtt*=4.0; continue; }
                if (x+y>dd) t-=dtt;
                }
            if (t>1.0) break;
            a=da*t;
            x0=t*cos(a); pnt.add(x0);
            y0=t*sin(a); pnt.add(y0);
            as2(i0,i);
            }
        }
    if (n==3)
        {
        // r=1,d=!,screws=?
        // S = 4*PI*r^2
        // screws = 2*PI*r/(2*d)
        // points = S/d^2
        int i0,i;
        double a,b,da,db,t,dt,dtt;
        double x,y,z,x0,y0,z0;
        double screws=M_PI/d;
        double points=4.0*M_PI/(d*d);
        dbg=points;
        da=    M_PI;
        db=2.0*M_PI*screws;
        x0=1.0; pnt.add(x0);
        y0=0.0; pnt.add(y0);
        z0=0.0; pnt.add(z0);
        dt=0.1*(1.0/points);
        for (t=0.0,i0=0,i=1;;i0=i,i++)
            {
            for (dtt=dt,j=0;j<10;j++,dtt*=0.5)
                {
                t+=dtt;
                a=da*t;
                b=db*t;
                x=cos(a)       -x0; x*=x;
                y=sin(a)*cos(b)-y0; y*=y;
                z=sin(a)*sin(b)-z0; z*=z;
                if ((!j)&&(x+y+z<dd)){ j--; t-=dtt; dtt*=4.0; continue; }
                if (x+y+z>dd) t-=dtt;
                }
            if (t>1.0) break;
            a=da*t;
            b=db*t;
            x0=cos(a)       ; pnt.add(x0);
            y0=sin(a)*cos(b); pnt.add(y0);
            z0=sin(a)*sin(b); pnt.add(z0);
            as2(i0,i);
            }
        }
    if (n==4)
        {
        // r=1,d=!,screws=?
        // S = 2*PI^2*r^3
        // screws = 2*PI*r/(2*d)
        // points = 3*PI^3/(4*d^3);
        int i0,i;
        double a,b,c,da,db,dc,t,dt,dtt;
        double x,y,z,w,x0,y0,z0,w0;
        double screws = M_PI/d;
        double points=3.0*M_PI*M_PI*M_PI/(4.0*d*d*d);
        dbg=points;
        da=    M_PI;
        db=    M_PI*screws;
        dc=2.0*M_PI*screws*screws;
        x0=1.0; pnt.add(x0);
        y0=0.0; pnt.add(y0);
        z0=0.0; pnt.add(z0);
        w0=0.0; pnt.add(w0);
        dt=0.1*(1.0/points);
        for (t=0.0,i0=0,i=1;;i0=i,i++)
            {
            for (dtt=dt,j=0;j<10;j++,dtt*=0.5)
                {
                t+=dtt;
                a=da*t;
                b=db*t;
                c=dc*t;
                x=cos(a)              -x0; x*=x;
                y=sin(a)*cos(b)       -y0; y*=y;
                z=sin(a)*sin(b)*cos(c)-z0; z*=z;
                w=sin(a)*sin(b)*sin(c)-w0; w*=w;
                if ((!j)&&(x+y+z+w<dd)){ j--; t-=dtt; dtt*=4.0; continue; }
                if (x+y+z+w>dd) t-=dtt;
                } dt=dtt;
            if (t>1.0) break;
            a=da*t;
            b=db*t;
            c=dc*t;
            x0=cos(a)              ; pnt.add(x0);
            y0=sin(a)*cos(b)       ; pnt.add(y0);
            z0=sin(a)*sin(b)*cos(c); pnt.add(z0);
            w0=sin(a)*sin(b)*sin(c); pnt.add(w0);
            as2(i0,i);
            }
        }

    for (i=0;i<pnt.num;i++) pnt.dat[i]*=r;
    for (i=0;i<s1.num;i++) s1.dat[i]*=n;
    for (i=0;i<s2.num;i++) s2.dat[i]*=n;
    for (i=0;i<s3.num;i++) s3.dat[i]*=n;
    for (i=0;i<s4.num;i++) s4.dat[i]*=n;
    }
//---------------------------------------------------------------------------
void ND_mesh::glDraw(ND_reper &rep,dim_reduction *cfg,int render)
    {
    int N,i,j,i0,i1,i2,i3;
    const int n0=0,n1=n,n2=n+n,n3=n2+n,n4=n3+n;
    double a,b,w,F,*p0,*p1,*p2,*p3,_zero=1e-6;
    vector<4> v;
    List<double> tmp,t0;        // temp
    List<double> S1,S2,S3,S4;   // reduced simplexes
    #define _swap(aa,bb) { double *p=aa.dat; aa.dat=bb.dat; bb.dat=p; int q=aa.siz; aa.siz=bb.siz; bb.siz=q; q=aa.num; aa.num=bb.num; bb.num=q; }

    // apply transform matrix pnt -> tmp
    tmp.allocate(pnt.num); tmp.num=pnt.num;
    for (i=0;i<pnt.num;i+=n)
        {
        v.ld(0.0,0.0,0.0,0.0);
        for (j=0;j<n;j++) v.a[j]=pnt.dat[i+j];
        rep.l2g(v,v);
        for (j=0;j<n;j++) tmp.dat[i+j]=v.a[j];
        }
    // copy simplexes and convert point indexes to points (only due to cross section)
    S1.allocate(s1.num*n); S1.num=0; for (i=0;i<s1.num;i++) for (j=0;j<n;j++) S1.add(tmp.dat[s1.dat[i]+j]);
    S2.allocate(s2.num*n); S2.num=0; for (i=0;i<s2.num;i++) for (j=0;j<n;j++) S2.add(tmp.dat[s2.dat[i]+j]);
    S3.allocate(s3.num*n); S3.num=0; for (i=0;i<s3.num;i++) for (j=0;j<n;j++) S3.add(tmp.dat[s3.dat[i]+j]);
    S4.allocate(s4.num*n); S4.num=0; for (i=0;i<s4.num;i++) for (j=0;j<n;j++) S4.add(tmp.dat[s4.dat[i]+j]);

    // reduce dimensions
    for (N=n;N>2;)
        {
        N--;
        if (cfg[N].view==_view_Orthographic){}  // no change
        if (cfg[N].view==_view_Perspective)
            {
            w=cfg[N].coordinate;
            F=cfg[N].focal_length;
            for (i=0;i<S1.num;i+=n)
                {
                a=S1.dat[i+N]-w;
                if (a>=F) a=F/a; else a=0.0;
                for (j=0;j<n;j++) S1.dat[i+j]*=a;
                }
            for (i=0;i<S2.num;i+=n)
                {
                a=S2.dat[i+N]-w;
                if (a>=F) a=F/a; else a=0.0;
                for (j=0;j<n;j++) S2.dat[i+j]*=a;
                }
            for (i=0;i<S3.num;i+=n)
                {
                a=S3.dat[i+N]-w;
                if (a>=F) a=F/a; else a=0.0;
                for (j=0;j<n;j++) S3.dat[i+j]*=a;
                }
            for (i=0;i<S4.num;i+=n)
                {
                a=S4.dat[i+N]-w;
                if (a>=F) a=F/a; else a=0.0;
                for (j=0;j<n;j++) S4.dat[i+j]*=a;
                }
            }
        if (cfg[N].view==_view_CrossSection)
            {
            w=cfg[N].coordinate;
            _swap(S1,tmp); for (S1.num=0,i=0;i<tmp.num;i+=n1)               // points
                {
                p0=tmp.dat+i+n0;
                if (fabs(p0[N]-w)<=_zero)
                    {
                    for (j=0;j<n;j++) S1.add(p0[j]);
                    }
                }
            _swap(S2,tmp); for (S2.num=0,i=0;i<tmp.num;i+=n2)               // lines
                {
                p0=tmp.dat+i+n0;              a=p0[N];              b=p0[N];// a=min,b=max
                p1=tmp.dat+i+n1; if (a>p1[N]) a=p1[N]; if (b<p1[N]) b=p1[N];
                if (fabs(a-w)+fabs(b-w)<=_zero)                             // fully inside
                    {
                    for (j=0;j<n;j++) S2.add(p0[j]);
                    for (j=0;j<n;j++) S2.add(p1[j]);
                    continue;
                    }
                if ((a<=w)&&(b>=w))                                         // intersection -> points
                    {
                    a=(w-p0[N])/(p1[N]-p0[N]);
                    for (j=0;j<n;j++) S1.add(p0[j]+a*(p1[j]-p0[j]));
                    }
                }
            _swap(S3,tmp); for (S3.num=0,i=0;i<tmp.num;i+=n3)               // triangles
                {
                p0=tmp.dat+i+n0;              a=p0[N];              b=p0[N];// a=min,b=max
                p1=tmp.dat+i+n1; if (a>p1[N]) a=p1[N]; if (b<p1[N]) b=p1[N];
                p2=tmp.dat+i+n2; if (a>p2[N]) a=p2[N]; if (b<p2[N]) b=p2[N];
                if (fabs(a-w)+fabs(b-w)<=_zero)                             // fully inside
                    {
                    for (j=0;j<n;j++) S3.add(p0[j]);
                    for (j=0;j<n;j++) S3.add(p1[j]);
                    for (j=0;j<n;j++) S3.add(p2[j]);
                    continue;
                    }
                if ((a<=w)&&(b>=w))                                         // cross section -> t0
                    {
                    t0.num=0;
                    i0=0; if (p0[N]<w-_zero) i0=1; if (p0[N]>w+_zero) i0=2;
                    i1=0; if (p1[N]<w-_zero) i1=1; if (p1[N]>w+_zero) i1=2;
                    i2=0; if (p2[N]<w-_zero) i2=1; if (p2[N]>w+_zero) i2=2;
                    if (i0+i1==3){ a=(w-p0[N])/(p1[N]-p0[N]); for (j=0;j<n;j++) t0.add(p0[j]+a*(p1[j]-p0[j])); }
                    if (i1+i2==3){ a=(w-p1[N])/(p2[N]-p1[N]); for (j=0;j<n;j++) t0.add(p1[j]+a*(p2[j]-p1[j])); }
                    if (i2+i0==3){ a=(w-p2[N])/(p0[N]-p2[N]); for (j=0;j<n;j++) t0.add(p2[j]+a*(p0[j]-p2[j])); }
                    if (!i0) for (j=0;j<n;j++) t0.add(p0[j]);
                    if (!i1) for (j=0;j<n;j++) t0.add(p1[j]);
                    if (!i2) for (j=0;j<n;j++) t0.add(p2[j]);
                    if (t0.num==n1) for (j=0;j<t0.num;j++) S1.add(t0.dat[j]);// copy t0 to target simplex based on points count
                    if (t0.num==n2) for (j=0;j<t0.num;j++) S2.add(t0.dat[j]);
                    if (t0.num==n3) for (j=0;j<t0.num;j++) S3.add(t0.dat[j]);
                    }
                }
            _swap(S4,tmp); for (S4.num=0,i=0;i<tmp.num;i+=n4)               // tetrahedrons
                {
                p0=tmp.dat+i+n0;              a=p0[N];              b=p0[N];// a=min,b=max
                p1=tmp.dat+i+n1; if (a>p1[N]) a=p1[N]; if (b<p1[N]) b=p1[N];
                p2=tmp.dat+i+n2; if (a>p2[N]) a=p2[N]; if (b<p2[N]) b=p2[N];
                p3=tmp.dat+i+n3; if (a>p3[N]) a=p3[N]; if (b<p3[N]) b=p3[N];
                if (fabs(a-w)+fabs(b-w)<=_zero)                             // fully inside
                    {
                    for (j=0;j<n;j++) S4.add(p0[j]);
                    for (j=0;j<n;j++) S4.add(p1[j]);
                    for (j=0;j<n;j++) S4.add(p2[j]);
                    for (j=0;j<n;j++) S4.add(p3[j]);
                    continue;
                    }
                if ((a<=w)&&(b>=w))                                         // cross section -> t0
                    {
                    t0.num=0;
                    i0=0; if (p0[N]<w-_zero) i0=1; if (p0[N]>w+_zero) i0=2;
                    i1=0; if (p1[N]<w-_zero) i1=1; if (p1[N]>w+_zero) i1=2;
                    i2=0; if (p2[N]<w-_zero) i2=1; if (p2[N]>w+_zero) i2=2;
                    i3=0; if (p3[N]<w-_zero) i3=1; if (p3[N]>w+_zero) i3=2;
                    if (i0+i1==3){ a=(w-p0[N])/(p1[N]-p0[N]); for (j=0;j<n;j++) t0.add(p0[j]+a*(p1[j]-p0[j])); }
                    if (i1+i2==3){ a=(w-p1[N])/(p2[N]-p1[N]); for (j=0;j<n;j++) t0.add(p1[j]+a*(p2[j]-p1[j])); }
                    if (i2+i0==3){ a=(w-p2[N])/(p0[N]-p2[N]); for (j=0;j<n;j++) t0.add(p2[j]+a*(p0[j]-p2[j])); }
                    if (i0+i3==3){ a=(w-p0[N])/(p3[N]-p0[N]); for (j=0;j<n;j++) t0.add(p0[j]+a*(p3[j]-p0[j])); }
                    if (i1+i3==3){ a=(w-p1[N])/(p3[N]-p1[N]); for (j=0;j<n;j++) t0.add(p1[j]+a*(p3[j]-p1[j])); }
                    if (i2+i3==3){ a=(w-p2[N])/(p3[N]-p2[N]); for (j=0;j<n;j++) t0.add(p2[j]+a*(p3[j]-p2[j])); }
                    if (!i0) for (j=0;j<n;j++) t0.add(p0[j]);
                    if (!i1) for (j=0;j<n;j++) t0.add(p1[j]);
                    if (!i2) for (j=0;j<n;j++) t0.add(p2[j]);
                    if (!i3) for (j=0;j<n;j++) t0.add(p3[j]);
                    if (t0.num==n1) for (j=0;j<t0.num;j++) S1.add(t0.dat[j]);// copy t0 to target simplex based on points count
                    if (t0.num==n2) for (j=0;j<t0.num;j++) S2.add(t0.dat[j]);
                    if (t0.num==n3) for (j=0;j<t0.num;j++) S3.add(t0.dat[j]);
                    if (t0.num==n4) for (j=0;j<t0.num;j++) S4.add(t0.dat[j]);
                    }
                }
            }
        }
    glColor4ubv((BYTE*)(&col));
    if (render==_render_Wireframe)
        {
        // add points from higher primitives
        for (i=0;i<S2.num;i++) S1.add(S2.dat[i]);
        for (i=0;i<S3.num;i++) S1.add(S3.dat[i]);
        for (i=0;i<S4.num;i++) S1.add(S4.dat[i]);
        glPointSize(5.0);
        glBegin(GL_POINTS);
        glNormal3d(0.0,0.0,1.0);
        if (n==2) for (i=0;i<S1.num;i+=n1) glVertex2dv(S1.dat+i);
        if (n>=3) for (i=0;i<S1.num;i+=n1) glVertex3dv(S1.dat+i);
        glEnd();
        glPointSize(1.0);
        glBegin(GL_LINES);
        glNormal3d(0.0,0.0,1.0);
        if (n==2)
            {
            for (i=0;i<S2.num;i+=n1) glVertex2dv(S2.dat+i);
            for (i=0;i<S3.num;i+=n3)
                {
                glVertex2dv(S3.dat+i+n0); glVertex2dv(S3.dat+i+n1);
                glVertex2dv(S3.dat+i+n1); glVertex2dv(S3.dat+i+n2);
                glVertex2dv(S3.dat+i+n2); glVertex2dv(S3.dat+i+n0);
                }
            for (i=0;i<S4.num;i+=n4)
                {
                glVertex2dv(S4.dat+i+n0); glVertex2dv(S4.dat+i+n1);
                glVertex2dv(S4.dat+i+n1); glVertex2dv(S4.dat+i+n2);
                glVertex2dv(S4.dat+i+n2); glVertex2dv(S4.dat+i+n0);
                glVertex2dv(S4.dat+i+n0); glVertex2dv(S4.dat+i+n3);
                glVertex2dv(S4.dat+i+n1); glVertex2dv(S4.dat+i+n3);
                glVertex2dv(S4.dat+i+n2); glVertex2dv(S4.dat+i+n3);
                }
            }
        if (n>=3)
            {
            for (i=0;i<S2.num;i+=n1) glVertex3dv(S2.dat+i);
            for (i=0;i<S3.num;i+=n3)
                {
                glVertex3dv(S3.dat+i+n0); glVertex3dv(S3.dat+i+n1);
                glVertex3dv(S3.dat+i+n1); glVertex3dv(S3.dat+i+n2);
                glVertex3dv(S3.dat+i+n2); glVertex3dv(S3.dat+i+n0);
                }
            for (i=0;i<S4.num;i+=n4)
                {
                glVertex3dv(S4.dat+i+n0); glVertex3dv(S4.dat+i+n1);
                glVertex3dv(S4.dat+i+n1); glVertex3dv(S4.dat+i+n2);
                glVertex3dv(S4.dat+i+n2); glVertex3dv(S4.dat+i+n0);
                glVertex3dv(S4.dat+i+n0); glVertex3dv(S4.dat+i+n3);
                glVertex3dv(S4.dat+i+n1); glVertex3dv(S4.dat+i+n3);
                glVertex3dv(S4.dat+i+n2); glVertex3dv(S4.dat+i+n3);
                }
            }
        glEnd();
        }
    if (render==_render_Polygon)
        {
        double nor[3],a[3],b[3],q;
        #define _triangle2(ss,p0,p1,p2)                 \
            {                                           \
            glVertex2dv(ss.dat+i+p0);                   \
            glVertex2dv(ss.dat+i+p1);                   \
            glVertex2dv(ss.dat+i+p2);                   \
            }
        #define _triangle3(ss,p0,p1,p2)                 \
            {                                           \
            for(j=0;(j<3)&&(j<n);j++)                   \
                {                                       \
                a[j]=ss.dat[i+p1+j]-ss.dat[i+p0+j];     \
                b[j]=ss.dat[i+p2+j]-ss.dat[i+p1+j];     \
                }                                       \
            for(;j<3;j++){ a[j]=0.0; b[j]=0.0; }        \
            nor[0]=(a[1]*b[2])-(a[2]*b[1]);             \
            nor[1]=(a[2]*b[0])-(a[0]*b[2]);             \
            nor[2]=(a[0]*b[1])-(a[1]*b[0]);             \
            q=sqrt((nor[0]*nor[0])+(nor[1]*nor[1])+(nor[2]*nor[2]));    \
            if (q>1e-10) q=1.0/q; else q-0.0;           \
            for (j=0;j<3;j++) nor[j]*=q;                \
            glNormal3dv(nor);                           \
            glVertex3dv(ss.dat+i+p0);                   \
            glVertex3dv(ss.dat+i+p1);                   \
            glVertex3dv(ss.dat+i+p2);                   \
            }
        #define _triangle3b(ss,p0,p1,p2)                \
            {                                           \
            glNormal3dv(nor3.dat+(i/n));                \
            glVertex3dv(ss.dat+i+p0);                   \
            glVertex3dv(ss.dat+i+p1);                   \
            glVertex3dv(ss.dat+i+p2);                   \
            }
        glBegin(GL_TRIANGLES);
        if (n==2)
            {
            glNormal3d(0.0,0.0,1.0);
            for (i=0;i<S3.num;i+=n3) _triangle2(S3,n0,n1,n2);
            for (i=0;i<S4.num;i+=n4)
                {
                _triangle2(S4,n0,n1,n2);
                _triangle2(S4,n3,n0,n1);
                _triangle2(S4,n3,n1,n2);
                _triangle2(S4,n3,n2,n0);
                }
            }
        if (n>=3)
            {
            for (i=0;i<S3.num;i+=n3) _triangle3 (S3,n0,n1,n2);
            for (i=0;i<S4.num;i+=n4)
                {
                _triangle3(S4,n0,n1,n2);
                _triangle3(S4,n3,n0,n1);
                _triangle3(S4,n3,n1,n2);
                _triangle3(S4,n3,n2,n0);
                }
            glNormal3d(0.0,0.0,1.0);
            }
        glEnd();
        #undef _triangle2
        #undef _triangle3
        }
    #undef _swap
    }
//---------------------------------------------------------------------------
#undef _cube
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------

我使用我的动态列表模板是这样的


List<double> xxx;double xxx[];相同
xxx.add(5);5添加到列表的末尾
xxx[7]访问数组元素(安全)
xxx.dat[7]访问数组元素(不安全但快速的直接访问)
xxx.num是数组的实际使用大小
xxx.reset()清除数组并设置xxx.num=0
xxx.allocate(100)100个项目预分配空间

,因此您需要将其移植到您可以使用的任何列表中(例如std:vector<>)。我还使用5x5变换矩阵,

void ND_reper::g2l    (vector<4> &l,vector<4> &g);  // global xyzw -> local xyzw
void ND_reper::l2g    (vector<4> &g,vector<4> &l);  // global xyzw <- local xyzw

将点转换为全局或局部坐标(通过将正矩阵或逆矩阵乘以点)。您可以忽略它,因为它仅在渲染中使用过一次,您可以改为复制这些点(不旋转)...在同一标头中还包含一些常量:

const double pi   =    M_PI;
const double pi2  =2.0*M_PI;
const double pipol=0.5*M_PI;
const double deg=M_PI/180.0;
const double rad=180.0/M_PI;

我也将矢量和矩阵数学模板集成在转换矩阵头中,因此vector<n>是n维向量,matrix<n>n*n方阵,但是它仅用于渲染,因此再次可以忽略它。如果您对此感兴趣,那么所有链接都来自其中:

枚举和降维仅用于渲染。 cfg包含如何将每个尺寸缩小到2D。

AnsiString是来自 VCL 的自重定位字符串,因此可以使用char*或环境中的字符串类。 DWORD只是无符号的32位int。希望我没有忘记什么...

答案 1 :(得分:2)

我对如何执行此操作有另一个疯狂的想法。它与我以前的方法完全不同,因此有了新的答案...

另外一个答案是建议在超立方体表面上创建点的均匀分布,然后将到超立方体中心的点距离标准化为超空间的半径,并将其用于排斥粒子模拟。过去,我对3D效果很好,但在3D情况下,由于像BVH这样的结构,尺寸过大会非常缓慢或复杂。

但是这让我开始思考如何倒退。因此,将超立方体上的点非线性地分布,因此在归一化之后,这些点就线性地分布在超球体上。

让我们从简单的2D开始

2D

因此,我们只需在+/-45 deg之间步进角度并计算绿点。角度步长da必须精确地划分90 deg并给出点密度。因此,所有脸部的所有2D点将是+/-1.0tan(angle)的组合。

完成所有点后,只需简单地计算每个点的大小即可使其居中并重新缩放,使其等于超球面半径。

可以轻松扩展到任意维度

2D以上的每个尺寸只需为循环角度添加一个即可进行迭代。

以下是我以前的回答中使用我的引擎的2D,3D,4D C ++示例:

void ND_mesh::set_HyperSpherePCL(int N,double r,double da)
    {
    reset(N);

    int na=floor(90.0*deg/da);
    if (na<1) return;
    da=90.0*deg/double(na-1);

    if (n==2)
        {
        int i;
        double a,x,y,l;
        for (a=-45.0*deg,i=0;i<na;i++,a+=da)
            {
            x=tan(a); y=1.0;
            l=sqrt((x*x)+(y*y));
            x/=l; y/=l;
            pnt.add( x); pnt.add(-y);
            pnt.add( x); pnt.add(+y);
            pnt.add(-y); pnt.add( x);
            pnt.add(+y); pnt.add( x);
            }
        }
    if (n==3)
        {
        int i,j;
        double a,b,x,y,z,l;
        for (a=-45.0*deg,i=0;i<na;i++,a+=da)
         for (b=-45.0*deg,j=0;j<na;j++,b+=da)
            {
            x=tan(a); y=tan(b); z=1.0;
            l=sqrt((x*x)+(y*y)+(z*z));
            x/=l; y/=l; z/=l;
            pnt.add( x); pnt.add( y); pnt.add(-z);
            pnt.add( x); pnt.add( y); pnt.add(+z);
            pnt.add( x); pnt.add(-z); pnt.add( y);
            pnt.add( x); pnt.add(+z); pnt.add( y);
            pnt.add(-z); pnt.add( x); pnt.add( y);
            pnt.add(+z); pnt.add( x); pnt.add( y);
            }
        }
    if (n==4)
        {
        int i,j,k;
        double a,b,c,x,y,z,w,l;
        for (a=-45.0*deg,i=0;i<na;i++,a+=da)
         for (b=-45.0*deg,j=0;j<na;j++,b+=da)
          for (c=-45.0*deg,k=0;k<na;k++,c+=da)
            {
            x=tan(a); y=tan(b); z=tan(c); w=1.0;
            l=sqrt((x*x)+(y*y)+(z*z)+(w*w));
            x/=l; y/=l; z/=l; w/=l;
            pnt.add( x); pnt.add( y); pnt.add( z); pnt.add(-w);
            pnt.add( x); pnt.add( y); pnt.add( z); pnt.add(+w);
            pnt.add( x); pnt.add( y); pnt.add(-w); pnt.add( z);
            pnt.add( x); pnt.add( y); pnt.add(+w); pnt.add( z);
            pnt.add( x); pnt.add(-w); pnt.add( y); pnt.add( z);
            pnt.add( x); pnt.add(+w); pnt.add( y); pnt.add( z);
            pnt.add(-w); pnt.add( x); pnt.add( y); pnt.add( z);
            pnt.add(+w); pnt.add( x); pnt.add( y); pnt.add( z);
            }
        }

    for (int i=0;i<pnt.num/n;i++) as1(i);
    rescale(r,n);
    }
//---------------------------------------------------------------------------

n=N是维度r是半径,da[rad]中的角度步长。

和2D / 3D / 4D透视图预览:

2D 3D 4D

这里有更多的点和更好的3D尺寸:

3D

立方体图案稍微可见,但点距对我来说还可以。在GIF上很难看到它,因为背面的点正与前面的点合并...

这是2D正方形和3D立方体,没有对球体进行归一化:

2D square 3D cube

如您所见,边缘上的点密度要小得多...

预览仅使用透视投影,因为这不会生成网格拓扑,而只是点,因此无法执行横截面...

还请注意,这会在边缘上产生一些重复的点(我认为,对于某些反射镜,将角度循环少一遍就可以解决这一问题,但太懒了以致无法实现)

答案 2 :(得分:0)

作为部分答案,您可以使用Newton's method计算f的倒数。在牛顿迭代中,将x用作起始点是一个不错的选择,因为f(x)x的距离不得超过1个单位。这是一个Python实现:

import math

def f(x):
    return x + 0.5*math.sin(2*x)

def f_inv(x,tol = 1e-8):
    xn = x
    y = f(xn)
    while abs(y-x) > tol:
        xn -= (y-x)/(1+math.cos(2*xn))
        y = f(xn)
    return xn

关于牛顿方法的这种应用,一个很好的事实是,每当cos(2*x) = -1(您将被0除)时,您都会自动拥有sin(2*x) = 0,从而得到f(x) = x。在这种情况下,永远不会进入while循环,f_inv只会返回原始x。

答案 3 :(得分:0)

我们有n个点,分别是P1,...,Pn。我们有一个维数d。每个(i = 1,n)点都可以表示为:

Pi =(pi(x1),...,pi(xd))

我们知道

D(Pi,0)= 1 <=>

sqrt((pi(x1)-pj(x1))^ 2 + ... +(pi(xd)-pj(xd))^ 2)= 1

以及任意两点之间的最小距离,MD为

MD <= D(Pi,Pj)

当且仅当MD不能更高时,解决方案才可以接受。

如果d = 2,则我们有一个圆并在其上放置点。圆形是具有以下属性的多边形:

  • 它有n个角度
  • n->无限
  • 每侧的长度相似

因此,当我们增加n时,n个角的多边形(其中n是一个有限数且大于2)也具有相似的长度,每个边的长度都接近一个圆。请注意,d = 2中的第一多边形是三角形。我们只有一个角度,最小角度单位为360度/ n。

现在,如果我们有一个正方形并在其上均匀分布点,则通过base transformation将正方形转换成圆形应该是精确的解决方案,或者非常接近。如果是精确解,那么对于d = 2的情况,这是一个简单的解决方案。如果仅 非常接近,则可以通过近似方法确定a内的解。给定您选择的精度。

在d = 3的情况下,我将使用此想法。我将解决多维数据集的问题,该问题要简单得多,并使用基本转换将多维数据集点转换为我的球体点。我将在d> 3上使用此方法,解决超立方体的问题并将其转换为超球体。当您将点均匀分布在d维超立方体上时,请使用曼哈顿距离。

请注意,我不知道转换为超球面的超立方体的解是精确解还是只是接近解,但是如果不是精确解,那么我们可以近似地提高精度。

因此,这种方法是解决问题的方法,从时间复杂度的角度来看,它不一定是最佳方法,因此,如果有人深入研究了斐波那契晶格区域并知道如何将其推广到更多维度,则/她的答案可能比我的答案更好。

如果定义f(x)的Taylor series,则可以确定f(x)= x-0.5sin2x的倒数。您将获得x which can be inverted的多项式级数。

答案 4 :(得分:0)

所有先前的答案都有效,但是仍然缺少实际代码。遗漏了两个真实的部分,通常可以实现。

  1. 我们需要计算sin^(d-2)(x)的积分。如果您按部分进行递归集成,则此表单为封闭形式。在这里,我以递归的方式实现了它,尽管对于维度〜> 100,我发现sin^d的数值积分更快
  2. 我们需要计算该积分的反函数,对于sin^dd > 1没有一个闭合形式。尽管其他答案中可能有更好的方法,但在这里我使用二进制搜索来计算它。

这两个方法与生成素数的方法相结合,产生了完整的算法:

from itertools import count, islice
from math import cos, gamma, pi, sin, sqrt
from typing import Callable, Iterator, List

def int_sin_m(x: float, m: int) -> float:
    """Computes the integral of sin^m(t) dt from 0 to x recursively"""
    if m == 0:
        return x
    elif m == 1:
        return 1 - cos(x)
    else:
        return (m - 1) / m * int_sin_m(x, m - 2) - cos(x) * sin(x) ** (
            m - 1
        ) / m

def primes() -> Iterator[int]:
    """Returns an infinite generator of prime numbers"""
    yield from (2, 3, 5, 7)
    composites = {}
    ps = primes()
    next(ps)
    p = next(ps)
    assert p == 3
    psq = p * p
    for i in count(9, 2):
        if i in composites:  # composite
            step = composites.pop(i)
        elif i < psq:  # prime
            yield i
            continue
        else:  # composite, = p*p
            assert i == psq
            step = 2 * p
            p = next(ps)
            psq = p * p
        i += step
        while i in composites:
            i += step
        composites[i] = step

def inverse_increasing(
    func: Callable[[float], float],
    target: float,
    lower: float,
    upper: float,
    atol: float = 1e-10,
) -> float:
    """Returns func inverse of target between lower and upper

    inverse is accurate to an absolute tolerance of atol, and
    must be monotonically increasing over the interval lower
    to upper    
    """
    mid = (lower + upper) / 2
    approx = func(mid)
    while abs(approx - target) > atol:
        if approx > target:
            upper = mid
        else:
            lower = mid
        mid = (upper + lower) / 2
        approx = func(mid)
    return mid

def uniform_hypersphere(d: int, n: int) -> List[List[float]]:
    """Generate n points over the d dimensional hypersphere"""
    assert d > 1
    assert n > 0
    points = [[1 for _ in range(d)] for _ in range(n)]
    for i in range(n):
        t = 2 * pi * i / n
        points[i][0] *= sin(t)
        points[i][1] *= cos(t)
    for dim, prime in zip(range(2, d), primes()):
        offset = sqrt(prime)
        mult = gamma(dim / 2 + 0.5) / gamma(dim / 2) / sqrt(pi)

        def dim_func(y):
            return mult * int_sin_m(y, dim - 1)

        for i in range(n):
            deg = inverse_increasing(dim_func, i * offset % 1, 0, pi)
            for j in range(dim):
                points[i][j] *= sin(deg)
            points[i][dim] *= cos(deg)
    return points

哪个在球体上为200个点生成以下图像:

sphere distribution