如何在(旧)opengl (2.4) 中按程序绘制 (hyper/n-) 立方体

How to procedurally draw a (hyper/n-)cube in (old) opengl (2.4)

这几个星期以来一直困扰着我,我没有结束对它的研究,因为我目前超负荷并且它让我落后于第一次让我研究这个的第一年 CS (opengl) 大学课程: 如何只用一个for循环画出立方体的所有面

练习说我们先列出一个正方形的所有顶点的坐标,然后是一个立方体的所有顶点的坐标,然后是绘制一个。我很快注意到,因为它是一个立方体,并且所有长度都相等,因此映射到二进制。我注意到方形顶点坐标自然映射到这些页面上的 gray code which I studied formerly in digital electronics in first semester. I wondered if it was doing something for the cube and it in fact was going through all contiguuous vertices, what I then found to be called a hamiltonian path or an eulerian cycle (I’m not sure, I saw that linked to the snake in a box 问题,包括格雷码),但它不允许我计算每个(最好是连续的,所以我可以使用 GL_QUADS 甚至 GL_QUAD_STRIP) face 以便分解我绘制的立方体(虽然它应该允许我计算一个 - 我猜 - 特别排序的所有线的数组,也许然后我可以从那里计算每个面?)。

我也尝试用 opengl 转换来做,因为我们在课程中继续学习它们,我猜它可能会将更多计算推迟到 gpu 而不是 cpu,并且几乎成功了,但是我的 for 循环需要 8 次迭代,所以某处有 2 个无用的隐藏面孔:

void
face (void)
{
  glPushMatrix();
  {
    glTranslatef(0, 0, 0.5);
    glRectf(-0.5, -0.5, 0.5, 0.5);
  }
  glPopMatrix();
}

void
cube (void)
{
  for (unsigned int i=0; i < 8 ; ++i)
    {
      glColor3f(!(i&4), !(i&2), !(i&1));
      face();
      glRotatef(180, !!(i&4), !!(i&2), !!(i&1));
    }
}

我有点想继续我的研究,尽管我没有时间,而且通常最终失败的想法经常在我脑海中浮现。现在,每次我被要求为某物画一个立方体时,我都会花 1 个多小时继续这个。

让我兴奋的是,如果我成功分解立方体的过程计算,这可能是从维度的概念中抽象出来的,然后将相同的算法推广到第 n 维应该很容易,从而给出简单、自然和简单的方法来绘制一个 tesseract 或任何超立方体……这与我经常进行的长期实验相匹配,试图概括在第 n 维给我的所有东西(当他们要求时第一次这样做在二维中绘制螺旋线)。

PS:对于与维数space、n维、顶点分布、线、面的有序分布以及与格雷码的关系的所有内容,我还应该问一下吗在 math.stackexchange.com?

立方体的 6 个面可以通过围绕 x- 和 y-axis 交替旋转 90 度生成:

+---+
| 1 |
+---+---+
| 2 | 3 |
+---+---+---+
    | 4 | 5 |
    +---+---+
        | 6 |
        +---+

可以这样编码:

for( int i=0; i<6; ++i)
{ 
    glColor3f(!(i&4), !(i&2), !(i&1));
    face();
    glRotatef(90.0f, (float)(i%2), (float)(1-(i%2)), 0.0f);
}

预览:

void face (void)
{
    glPushMatrix();
    glTranslatef(0, 0, 0.5);
    glRectf(-0.3, -0.3, 0.3, 0.3);
    glPopMatrix();
}



下面给出了类似的结果。它与上面的不一样,因为立方体的边也围绕它们的法向量扭曲。

glRotatef(180.0f, (float)(i%2), (float)(1-(i%2)), 1.0f);

这是我在C++中想出的:

//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
#include "list.h"
//---------------------------------------------------------------------------
//--- ND Camera -------------------------------------------------------------
//---------------------------------------------------------------------------
const int ND = 4;           // dimensions
double focal_length=2.5;    // camera focal length (camera ais axis aligned)
double focus[ND];           // camera position (viewing in -Z direction)
double* perspective3D(double *_p)   // camera perspective projection of ND point p to 3D <-1,+1> position on xy plane (Z=0)
    {
    int i,j;
    double p[ND],w,dw;
    static double q[3]={0.0,0.0,0.0};
    // relative to camera
    for (i=0;i<ND;i++) p[i]=_p[i]-focus[i];
    // perspective projection
    for (w=1.0,i=ND-1;i>=2;i--)
        {
        if (p[i]<=1e-10)
         { for (i=0;i<3;i++) q[i]=0; return q; } // behind camera
        w*=focal_length/p[i];
        }
    for (j=0;j<2;j++) p[j]*=w;

    // conversion to double[3]=(x,y,0.0)
    for (i=0;(i<2)&&(i<ND);i++) q[i]=p[i]; q[2]=0.0;
    return q;
    }
//---------------------------------------------------------------------------
//--- ND Quad mesh ----------------------------------------------------------
//---------------------------------------------------------------------------
struct _quad        // Quad face
    {
    double p[4][ND];    // points
    _quad(){}
    _quad(_quad& a) { *this=a; }
    ~_quad(){}
    _quad* operator = (const _quad *a) { *this=*a; return this; }
    //_quad* operator = (const _quad &a) { ...copy... return this; }
    };
//---------------------------------------------------------------------------
void quad_hypercube(List<_quad> &quad,double a) // quad[] = hypercube (2a)^ND
    {
    if (ND<2) return;
    int i0,i1,j,k;
    double p[ND];
    _quad q;
    // set camera position and FOV
    for (j=0;j<ND;j++) focus[j]=0.0;
    for (j=2;j<ND;j++) focus[j]=-10.0*a/focal_length;   // perspective projected axises should have offset
    // clear faces
    quad.num=0;
    // perspective debug
    double z,w;
    #define add_quad(z,w) { q.p[0][0]=-a; q.p[0][1]=-a; q.p[0][2]=z; q.p[0][3]=w; q.p[1][0]=+a; q.p[1][1]=-a; q.p[1][2]=z; q.p[1][3]=w; q.p[2][0]=+a; q.p[2][1]=+a; q.p[2][2]=z; q.p[2][3]=w; q.p[3][0]=-a; q.p[3][1]=+a; q.p[3][2]=z; q.p[3][3]=w; quad.add(q); }

    // iterate through all axis aligned i[0]i1 planes combinations
    for (i0=0     ;i0<ND;i0++)
     for (i1=i0+1;i1<ND;i1++)
        {
        // start offset
        for (j=0;j<ND;j++) p[j]=-a; p[i0]=0; p[i1]=0;
        // iterate all offset combinations
        for (;;)
            {
            // add face
            for (j=0;j<ND;j++) q.p[0][j]=p[j]; q.p[0][i0]-=a; q.p[0][i1]-=a;
            for (j=0;j<ND;j++) q.p[1][j]=p[j]; q.p[1][i0]+=a; q.p[1][i1]-=a;
            for (j=0;j<ND;j++) q.p[2][j]=p[j]; q.p[2][i0]+=a; q.p[2][i1]+=a;
            for (j=0;j<ND;j++) q.p[3][j]=p[j]; q.p[3][i0]-=a; q.p[3][i1]+=a;
            quad.add(q);
            if (ND<=2) break;
            // increment offset
            for (k=0;;)
                {
                // first unused axis
                while((k==i0)||(k==i1)) k++;
                if (k>=ND) break;
                p[k]=-p[k];
                if (p[k]<0.0) k++;
                else break;
                }
            if (k>=ND) break;
            }
        }
    }
//---------------------------------------------------------------------------
void quad_draw(List<_quad> &quad)
    {
    for (int i=0;i<quad.num;i++)
        {
        glBegin(GL_QUADS);
        for (int j=0;j<4;j++)
         glVertex3dv(perspective3D(quad.dat[i].p[j]));
        glEnd();
        }
    }
//---------------------------------------------------------------------------

为了简单起见,我使用了我的动态列表模板:


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 项预分配 space

用法:

// global storage
List<_quad> quad;

// mesh init
quad_hypercube(quad,0.5);

// render
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// directional (normal shading)
float lightAmbient  [4]={0.50,0.50,0.50,1.00};      // rozptylene nesmerove
float lightDiffuse  [4]={0.50,0.50,0.50,1.00};      // smerove
float lightDirection[4]={0.00,0.00,+1.0,0.00};      // global smer svetla w=0
glLightfv(GL_LIGHT0,GL_AMBIENT ,lightAmbient );
glLightfv(GL_LIGHT0,GL_DIFFUSE ,lightDiffuse );
glLightfv(GL_LIGHT0,GL_POSITION,lightDirection);
glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE);
glEnable(GL_COLOR_MATERIAL);
glEnable(GL_LIGHT0);
glDisable(GL_LIGHTING);
glDepthFunc(GL_LEQUAL);
glDisable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);   

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glLineWidth(2.0);
glColor3f(0.5,0.5,0.5);
quad_draw(quad);
glLineWidth(1.0);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

glFlush();
SwapBuffers(hdc);

结果如下:

如您所见,4D+ 存在一些不一致,但生成的坐标看起来没问题,所以很可能是我从 ND[= 的投影出现了一些问题100=] 到 2D 某处(将在后面调查)因为内部立方体(由于可能错误的视角)在视觉上与另一个立方体的背面合并。我很可能需要在每个轴的基础上更改相机的 FOV

工作原理

简单地说,超立方体的每个面都轴对齐 QUAD 平行于基平面。所以 i0,i1 for 循环生成所有可能的基平面,我在其中编码 2D QUAD 坐标。

然后我为其他轴生成 +/-a 的所有组合,并为每个组合添加 QUAD 面。仅此而已。

为了更清楚,我们假设 3D 立方体和平面基面 xy。因此 i0=0; i1=1; 和平行于 xy 的立方体中的每个面将具有由格雷码编码的编码 4 点 x,y 坐标:

p0(-a,-a,?)
p1(-a,+a,?)
p2(+a,+a,?)
p3(+a,-a,?)

现在在 3D 中只剩下一个轴 (z) 所以对于 -a+a 的每个组合都会生成新面所以:

p0(-a,-a,-a) // xy face 1
p1(-a,+a,-a)
p2(+a,+a,-a)
p3(+a,-a,-a)

p0(-a,-a,+a) // xy face 2
p1(-a,+a,+a)
p2(+a,+a,+a)
p3(+a,-a,+a)

完成后移动到下一个平面 i0,i1 组合,即 xz

p0(-a,?,-a)
p1(-a,?,+a)
p2(+a,?,+a)
p3(+a,?,-a)

然后再次生成组合...

p0(-a,-a,-a) // xz face 1
p1(-a,-a,+a)
p2(+a,-a,+a)
p3(+a,-a,-a)

p0(-a,+a,-a) // xz face 2
p1(-a,+a,+a)
p2(+a,+a,+a)
p3(+a,+a,-a)

并继续,直到没有剩余的基面组合...

3D 中有 3 个基平面 (xy,xz,yz) 和 1 个剩余轴所以每个平面 2^1 = 2 个平行面所以 3*2 = 6 个面在一起。

4D 中你有 6 个基面 (xy,xz,xw,yz,yw,zw) 和 2 个剩余的轴给出了 +/-a 的 4 种组合意味着 2^2 = 4 平行面每个基平面通向 6*4 = 24 个面。