1

几周以来,这一直困扰着我,我没有结束对它的研究,因为我目前超负荷,它让我落后于第一年的 CS (opengl) 大学课程,这首先让我研究了这个:如何绘制所有只有一个 for 循环的立方体的面。

练习让我们列出一个正方形的所有顶点的坐标,然后是一个立方体的所有顶点的坐标,然后画一个。我很快注意到,因为它是一个立方体,并且所有长度都相等,因此映射到二进制。我注意到正方形顶点坐标自然映射到我在第一学期在数字电子学中学习的格雷码。我想知道它是否在为立方体做一些事情,实际上它正在通过所有连续的顶点,然后我发现它被称为汉密尔顿路径欧拉循环(我不确定,我看到这与蛇有关一个盒子这些页面上的问题,包括格雷码),但它不允许我计算每个(最好是连续的,所以我可以使用 GL_QUADS 甚至 GL_QUAD_STRIP)面来分解我绘制的立方体(尽管它应该允许我计算一个 - 我猜 - 所有行的特殊排序数组,也许然后我可以从那里计算每个面?)。

我还尝试使用 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 维应该很容易,从而给出一个简单、自然和简单的绘制正方体或任何超立方体的方法……这与我经常经历的很长的实验相匹配,试图概括在第 n 维必须给我的所有东西(当他们要求在 2D 中绘制螺旋时首先这样做)。

PS:关于维度空间的数学、n维、顶点分布、线、面的有序分布以及与格雷码的关系,我还应该在math.stackexchange.com上问吗?

4

2 回答 2

3

立方体的 6 个边可以通过围绕 x 轴和 y 轴交替旋转 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);
于 2018-04-05T21:58:19.037 回答
1

This is what I come up with in 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();
        }
    }
//---------------------------------------------------------------------------

to make this simple I used mine dynamic list template so:


List<double> xxx; is the same as double xxx[];
xxx.add(5); adds 5 to end of the list
xxx[7] access array element (safe)
xxx.dat[7] access array element (unsafe but fast direct access)
xxx.num is the actual used size of the array
xxx.reset() clears the array and set xxx.num=0
xxx.allocate(100) preallocate space for 100 items

usage:

// 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);

Here results:

hypercubes

As you can see for 4D+ there is some inconsistency but the generated coordinates looks OK so it is most likely some problem with my projection from ND to 2D somewhere (will investigate latter on) as the internal cubes (due to possibly wrong perspective) merges visually with the back face of the other cube. Most likely I would need to change the FOV of camera on per axis basis.

How it works

simply each face of the hypercube is axis aligned QUAD parallel to base plane. So the i0,i1 for loops generate all possible base planes in which I encode the 2D QUAD coordinates.

Then I generate all the combinations of +/-a for the other axises and add QUAD face for each combination. That is all.

To be more clear let assume 3D cube and plane face base plane xy. So i0=0; i1=1; and each face in cube parallel to xy will have the encoded 4 points x,y coordinates encoded by your gray code:

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

Now in 3D there is just one axis left (z) so for its each combination of -a and +a generate new face so:

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)

When done move to next plane i0,i1 combination which is xz

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

and then generate the combinations again ...

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)

and continue until no base plane combinations are left...

In 3D there are 3 base planes (xy,xz,yz) and 1 remainding axsis so 2^1 = 2 parallel faces per plane so 3*2 = 6 faces together.

In 4D you got 6 base planes (xy,xz,xw,yz,yw,zw) and 2 remainding axises give 4 combinations of +/-a meaning 2^2 = 4 parallel faces per base plane leading to 6*4 = 24 faces.

于 2018-04-10T15:05:35.257 回答