这里从OpenGL的角度来谈绘制花托的方法。
--写在前面的话
首先看一组花托的实例:
图1 花托的一个实例正面图
图2 花托的一个实例侧面图
既然选择手工绘制,必须进行数学分析了。首先要确定总体的设计算法:按照微积分的数学思想,我们可以选择一个“通用”的截面进行分析,这里很明显就是下图标记为红色的圆,然后让它绕原点旋转360°,就形成了花托(这是老外的说法,说成轮胎也无妨)。
为了方便描述,我们做些规定(可能与之前的“内圆”“外圆”的说法不同,但后面具体实现绘制花托时,就会看到它的方便了):
圆心O1和O2的距离,我们称之为大半径,用R表示,对应的圆称之为“大圆”;
以O2为圆心的圆的半径,我们称之为小半径,用r表示,对应的圆称之为“小圆”。
也就是说,“大圆”是我们正面看到的边界在花托中线的圆,“小圆”是我们侧面看到的一个截面。
从设计程序角度来说,绘制这个花托需要2层循环:外循环负责旋转“大圆”内的角度值,内循环负责旋转“小圆”内的角度值。“大圆”每旋转一个角度步长值,“小圆”要完成一圈的旋转。更进一步,我们在“大圆”上采集80个点(实际上每个点都是一个“小圆”的圆心),在“小圆”上采集40个点(GLint numMajor=80; GLint numMinor=40;)。
现在,我们来考虑在“小圆”上采集的这些点的坐标,这里使用俯瞰视图分析比较方便(实际上,下图的上下方向分别代表z轴的负正方向),至于圆截面上x轴的选取,我们选择大圆半径方向的延长线所在的直线为x轴----这样选取的好处在于,这条直线在xy平面上(关键就是这点),“小圆”边界上的点只要投影到这条直线上,再根据“大圆”内的旋转角度,就可以计算出“小圆”边界上的点在世界坐标系中的x和y坐标,而“小圆”边界上的点距离这条直线的距离其实就是世界坐标系中的z坐标值。
图4 俯瞰下的一个截面
而如果如下图选取x轴就会非常不方便:
图5 圆截面上不好的x轴选取方法(本地坐标系)
看着图4结合自己的三维空间想象力,我们可以得到“小圆”上的采样点的坐标:
说到这里,核心部分就完了。接下来,只要外层循环----“大圆”完成角度的360°旋转,一个最简易版的边框花托就绘制成了(代码见附录1):
图6 正面视图
图7 侧面视图
为了实现更好的效果,我们可以利用一个小技巧:使用三角带(GL_TRIANGLE_STRIP),相应地,程序实现上需要修改的地方:在外层循环中要一次采样2个点(代码见附录2)。
图8 使用三角带后的正面视图
图9 使用三角带后的侧面视图
附录1:使用GL_LINE_LOOP绘制轮胎
#include "StdAfx.h"
#define FREEGLUT_STATIC
#include <GLTools.h>
#include <math3d.h>
#include <math.h>
#include <GL/glut.h>
static GLfloat yRot=0.0f;
GLint nNumMajor=80,nNumMinor=40;//采样点的数目
GLfloat fStepMajor=2*M3D_PI/nNumMajor;//大圆旋转步长
GLfloat fStepMinor=2*M3D_PI/nNumMinor;
GLfloat fAngleMajor=0.0f;//大圆的旋转角度
GLfloat fAngleMinor=0.0f;
GLfloat x=0.0f,y=0.0f,z=0.0f;//空间一个顶点的坐标
GLfloat R=5.0f,r=2.0f;//大圆 小圆的半径
void SetupRC()
{
glClearColor(0.0f,0.0f,1.0f,1.0f);//blue background color
glColor3f(1.0f,1.0f,1.f);//white pen color
glEnable(GL_DEPTH_TEST);//开启深度测试
glEnable(GL_SMOOTH);
//glPolygonMode(GL_FORNT_AND_BACK,GL_LINE);
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除颜色 深度缓冲区
glPushMatrix();
glTranslatef(0.0f,0.0f,-25.0f);//使球体往屏幕里面平移
glRotatef(yRot,0.0f,1.0f,0.0f);//旋转
for(fAngleMajor=0.0f; fAngleMajor<=2*M3D_PI; fAngleMajor+=fStepMajor)
{
glBegin(GL_LINE_LOOP);
for(fAngleMinor=0.0f; fAngleMinor<=2*M3D_PI; fAngleMinor+=fStepMinor)
{
x=(r*cos(fAngleMinor)+R)*cos(fAngleMajor);
y=(r*cos(fAngleMinor)+R)*sin(fAngleMajor);
z=r*sin(fAngleMinor);
glVertex3f(x,y,z);
}
glEnd();
}
glPopMatrix();
glutSwapBuffers();
}
void ChangeSize(int w,int h)
{
GLfloat fAspect=(float)w/(float)h;
if(h==0)
h=1;
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0,fAspect,1.0,100.0);//"glu-" glOrtho
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glutPostRedisplay();
}
void TimerFunc(int value)
{
yRot+=2.0f;
glutPostRedisplay();
glutTimerFunc(100,TimerFunc,1);
}
int main(int argc,char** argv)
{
glutInit(&argc,argv);
glutInitWindowSize(800,600);
glutCreateWindow("Drawing a Torus");
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
glutDisplayFunc(RenderScene);
glutReshapeFunc(ChangeSize);
glutTimerFunc(100,TimerFunc,1);
SetupRC();
glutMainLoop();
return 0;
}
附录2:使用GL_TRIANGLE_STRIP绘制轮胎
#include "StdAfx.h"
#define FREEGLUT_STATIC
#include <GLTools.h>
#include <math3d.h>
#include <math.h>
#include <GL/glut.h>
static GLfloat yRot=60.0f;
GLint nNumMajor=80,nNumMinor=40;//采样点的数目
GLfloat fStepMajor=2*M3D_PI/nNumMajor;//大圆旋转步长
GLfloat fStepMinor=2*M3D_PI/nNumMinor;
GLfloat fAngleMajor1=0.0f,fAngleMajor2=0.0f;//大圆的旋转角度
GLfloat fAngleMinor=0.0f;
GLfloat vx1=0.0f,vy1=0.0f,vz1=0.0f,vx2=0.0f,vy2=0.0f,vz2=0.0f;//空间三角带的2个坐标
GLfloat R=5.0f,r=2.0f;//大圆 小圆的半径
void SetupRC()
{
glClearColor(0.0f,0.0f,1.0f,1.0f);//blue background color
glColor3f(1.0f,1.0f,1.f);//white pen color
glEnable(GL_DEPTH_TEST);//开启深度测试
glEnable(GL_SMOOTH);
glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
}
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);//清除颜色 深度缓冲区
glPushMatrix();
glTranslatef(0.0f,0.0f,-25.0f);
glRotatef(yRot,0.0f,1.0f,0.0f);//旋转
for(fAngleMajor1=0.0f; fAngleMajor1<=2*M3D_PI; fAngleMajor1+=fStepMajor)
{
fAngleMajor2=fAngleMajor1+fStepMajor;//大圆下一个采样点
glBegin(GL_TRIANGLE_STRIP);
for(fAngleMinor=0.0f; fAngleMinor<=2*M3D_PI+fStepMinor; fAngleMinor+=fStepMinor)//上限值2PI+fStepMinor否则会有一个裂痕
{
vx1=(r*cos(fAngleMinor)+R)*cos(fAngleMajor1);
vy1=(r*cos(fAngleMinor)+R)*sin(fAngleMajor1);
vz1=r*sin(fAngleMinor);
vx2=(r*cos(fAngleMinor)+R)*cos(fAngleMajor2);
vy2=(r*cos(fAngleMinor)+R)*sin(fAngleMajor2);
vz2=r*sin(fAngleMinor);
glVertex3f(vx1,vy1,vz1);
glVertex3f(vx2,vy2,vz2);
}
glEnd();
}
glPopMatrix();
glutSwapBuffers();
}
void ChangeSize(int w,int h)
{
GLfloat fAspect=(float)w/(float)h;
if(h==0)
h=1;
glViewport(0,0,w,h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0,fAspect,1.0,100.0);//"glu-" glOrtho
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glutPostRedisplay();
}
void TimerFunc(int value)
{
//yRot+=2.0f;
glutPostRedisplay();
glutTimerFunc(100,TimerFunc,1);
}
int main(int argc,char** argv)
{
glutInit(&argc,argv);
glutInitWindowSize(800,600);
glutCreateWindow("Drawing a Torus");
glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGBA|GLUT_DEPTH);
glutDisplayFunc(RenderScene);
glutReshapeFunc(ChangeSize);
glutTimerFunc(100,TimerFunc,1);
SetupRC();
glutMainLoop();
return 0;
}