课程目录:
课时1博客地址: http://blog.csdn.net/shuxieweilai/article/details/6587501 //给出设计魔方的大概思路
课时2博客地址: http://blog.csdn.net/shuxieweilai/article/details/6588164 //构造出一个魔方的模型,但不做任何处理
课时3博客地址: http://blog.csdn.net/shuxieweilai/article/details/6589543 //可实现魔方的放大缩小 以及整体旋转
课时4博客地址: http://blog.csdn.net/shuxieweilai/article/details/6592830 // 可以实现一个键的扭动魔方
本课程的代码以及演示程序可以到http://download.csdn.net/source/3423269 下载
本课时最终代码展示:
如要手工改变观察魔方的角度,可以到CCube类中的的CCube ::draw函数中被注释掉的glRotatef(45.0f, 10f, 0.0f);去掉注释,只需要改变里面的45度角的值,就可以看到魔方的其他面。
本节课的任务:
初始化出一个魔方,构造出一个魔方的模型。
好了 现在开始正题,通过上一课你应该了解了魔方制作的基本框架了,现在开始实现的第一步,构造出一个魔方的模型出来。
如果没有学过opengl的先看完nehe教程的前12个课程,包括编译环境的配置,nehe的课程中都有提到,本人直接采用了nehe的第12课时的原代码作为魔法的主程序:显示列表,但是本人并未用到显示列表,如果想将我的代码改成可以贴图的魔方,就应该运用显示列表了,不然的话你的程序会变成蜗牛的。你自己可以参考我提供下载本课程的代码,对nehe的代码进行删除,具体的就是删除显示列表的函数,以及drawglsence里面的代码,以及其他的一小部分的代码,我将主程序命名为mian.cpp。主程序里拷贝的代码不再说明,免得变得繁琐冗长,自觉的学习nehe的教程吧。
首先你得再main.cpp构造出一个魔方的全局对象, CMagicCube magiccube 位于LRESULT CALLBACK WndProc(……)之上,然后在int InitGL(GLvoid)函数里添加下面的代码magiccube.InitMagicCube();用来初始化魔方。好了现在先把main.cpp放在一边不管了,现在需要到Face.h里面添加下面的代码,我将会做出一一解释
static CPoint points[8] =
{
{-0.5, -0.5, -0.5}, //0 l d f
{-0.5, -0.5, 0.5}, //1 l d b
{-0.5, 0.5, -0.5}, //2 l u f
{-0.5, 0.5, 0.5}, //3 l u b
{0.5, -0.5, -0.5}, //4 r d f
{0.5, -0.5, 0.5}, //5 r d b
{0.5, 0.5, -0.5}, //6 r u f
{0.5, 0.5, 0.5} //7 r u b
};
static int points_index[6][4] =
{
//opengl建模,D3D下应有不同
{1,3,2,0},//left
{4,6,7,5},//right
{1,0,4,5},//down
{7,6,2,3},//up
{5,7,3,1},//front
{0,2,6,4}//back
};
static CColor init_colors[6]=
{
{1.0f, 1.0f, 1.0f},
{1.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f},
{0.0f, 0.0f, 1.0f},
{0.0f, 1.0f, 1.0f},
{1.0f, 1.0f, 0.0f}
};
首先看init_colors[6] 这个最简单了代表着初始化魔方的六种颜色。
points[8] 代表着一个立方体的8个顶点的坐标(当立方体的中心为(0,0,0) ) ;
index[6][4] 首先先将立方体的8个顶点用0到7的数字标号 注释中的l代表lefe u代表up 其他的也就不用我细说了吧。
好了 接着我们就开始初始化一个魔方,让魔方的获取各种信息。刚才我们说到magiccube.InitMagicCube ,
现在我们就跳到MagicCube.cpp的文件中,开始编写初始化函数。代码如下
void CMagicCube::InitMagicCube()
{
for(int i = -1; i < 2; i++)
for(int j = -1; j < 2; j++)
for(int z = -1; z < 2; z++)
{
m_cube[i+1][j+1][z+1].SetPoint((float)i,(float)j,(float)z);//设置每个立方体的中心位置
m_cube[i+1][j+1][z+1].InitCube();//初始化每个立方体
}
}
上面的代码运用了三重循环使得初始化了每个对应的立方体,而每次循环下标都是从-1开始到1,这是为了方便魔方位于屏幕中央,使得魔方的中心立方体的中心与(0,0,0)重合,现在我们跳到CCube.cpp里看看是如何初始化的,上面用到了两个函数 SetPoint 和InitCube,代码分别如下:
void CCube :: SetPoint(float a, float b, float c)
{
m_pt.m_x = a;
m_pt.m_y = b;
m_pt.m_z = c;
}
void CCube :: InitCube()
{
for(int i = 0; i< 6; i++)
{
for( int j = 0; j < 4; j++)
{
m_face[i].GetPeak(j) = points[ points_index[i][j] ];//设置立方体的每个顶点坐标
}
m_face[i].GetColorindex()i;//设置初始时的颜色
}
}
上面比较难理解的估计就是m_face[i].GetPeak(j) = points[ points_index[i][j] ]这个了,如果你不明白这句话的用意,好好的领会下上文中几个static变量的意义。希望能多通过自己的思考,如果还是不明白的话可以在博客中留言,我会替你详细阐述。
这里又调用了CFace里的GetPeak和GetColorindex不多说明了吧, 看名字就完全知道用意了吧。 OK到目前我们的初始化工作可以结束了,现在开始绘画出魔方。
重新回到Main.cpp 中在DrawGLScene(GLvoid) 函数中加入magiccube.draw()的代码。表示绘画魔方。
好现在跳入CMagicCube的类当中,实现绘画。代码如下
void CMagicCube::draw()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清楚深度缓存和颜色缓存
for(int i = -1; i < 2; i++)
for(int j = -1; j < 2; j++)
for(int z = -1; z < 2; z++)
{
m_cube[i+1][j+1][z+1].draw();
}
}
ok上面的代码glclear清除深度缓存和颜色缓存,下面的代码很容易看懂吧,那么就继续跳入CCube类中的draw代码。
void CCube ::draw()
{ glLoadIdentity();//重置投影矩阵
glTranslatef(0.0f, 0.0f, -10.0f);//j将坐标系移入深为10的地方。使得能够观看到整个魔方!
//glRotatef(45.0f, 1.0f, 1.0f, 0.0f); //改变坐标系的角度,从而可以观察到魔方的其他面
glTranslatef(m_pt.m_x*1.1, m_pt.m_y*1.1, m_pt.m_z*1.1);
for(int i = 0; i < 6; i++)
{
m_face[i].draw(i);
}
}
关于opengl的那些函数的意义我就不深入分析了,关键这句话glTranslatef(m_pt.m_x*1.1, m_pt.m_y*1.1, m_pt.m_z*1.1);得加以说明,如果没有乘以1.1倍的话句话的含义是将
坐标的原点移到当前绘制立方体的中心,那为啥又要乘以1.1倍呢,如果不这样做的话,会出现每个立方体之间没有间隔,使得相同的图片混为一起。下面的两张图片就是一个没有乘以1.1,一个do it。
乘了之后的:
好了 这里下面的代码也很简单了 最后我们来继续看 CFace里面的draw函数了。代码如下:
void CFace::draw(int index)
{
glColor3f(init_colors[index].m_r,init_colors[index].m_g,init_colors[index].m_b);//设置当前颜色
glBegin(GL_QUADS);//绘制矩形
for (int i=0; i<4; i++)
{
glVertex3f(m_peak[i].m_x, m_peak[i].m_y,m_peak[i].m_z);//循环找到相应的表面顶点坐标
}
glEnd();
}
貌似注释上我都写了 又没啥可讲的了,好了 有什么问题请博客留言吧,我会尽自己的努力给你解释。但有的可以百度的问题,希望你能先百度google之后再提问。
本篇又该结束了, 如想深入学习可以看的另一篇博客 魔方1.0版 第三课时. (明天将会更新)
ok 最后再来看看这里用到的一些c++ 知识
首先头文件里是不建议定义全局变量的。原因如下:
#include的意思,是指将被include 的.h文件加入到这个.c文件中,然后一起编译形成一个编译单元。比如a.c 和b.c都include com.h,而com.h里面定义一个部分int com; 那么在a.c生成的a.obj里面有一个com变量,而b.obj里面也有一个com变量。那么在把a.obj和b.obj连接成一个可以执行文件里,发现com在两个中间文件都有定义,那编译器肯定会报重复定义的错误
然而可以定义static变量,而且定义之后必须马上跟着初始化,比如上面的points【8】的数组,如果你在添加一句points【0】 = **;编译器将会报错。
在CFace类里面的两个获取函数 一个GetPeak 和一个GetColorindex,他们为啥要返回引用呢?
你可以在CCube类中找到这样两句话m_face[i].GetPeak(j) = points[ points_index[i][j] ; m_face[i].GetColorindex() = i; 很明显他们返回的值需要被当做左值来使用,而如果返回变量的话将会返回的是一个临时变量,临时变量只能充当右值使用,所以需要返回引用。