您自己的 OpenGL MFC 中的二次曲线





5.00/5 (10投票s)
在OpenGL MFC中,您可以根据自己的设计和通用多面体程序来实现替代的二次曲面。
引言
OpenGL中的二次曲面过程(gluSphere
、gluCilinder
、gluCone
等)是非常简单且有用的工具。但由于是闭源的,它们在纹理、颜色和形状表现方面存在限制和约束。有时,拥有一些具有可用代码的类似过程是值得的。
背景
OpenGL中具有开放源代码的半二次曲面实现是基于原始多边形性能开发的。演示项目GLPlane
源自我以前的CodeProject文章《使用CImage类在OpenGL中进行纹理映射》中的ImgTexture
项目(该项目也源自标准的MFC Cube Sample项目),并重命名为我以前的CodeProject文章《一键从现有代码创建MFC项目》的项目。
在开始构建提供的项目之前,强烈建议您查看随附的 演示文稿,以了解预期的输出。
演示说明
GLPlaneDemo
目录中的可执行文件GLPlane.exe是使用MSVS-2010的工具通过MSVS-2015 pro构建的。因此,GLPlane.exe甚至对Windows-XP也有效。如果您想在Windows-XP中运行,请不要忘记将mfc100.dll、mfc100u.dll、msvcp100.dll、msvcr100.dll文件放入GLPlaneDemo目录(或..windows\system32目录)。
一些菜单和一些特殊的加速键已安排好,以演示GLPlane项目的实现。
- 文件菜单->打开 - 选择用于纹理映射的图像文件。
- 文件菜单->播放 - 播放/停止对象旋转。
- 视图菜单->默认 - 原始Cube Sample性能。
- 视图菜单->颜色->选择颜色 - 使用标准的颜色对话框选择对象的颜色。
- 视图菜单->颜色->随机颜色 - 为每个面安装随机颜色。
- 视图菜单->纹理 - 当前纹理性能。
- 视图菜单->下一张纹理 - 下一张纹理性能(也可点击右箭头)。
- 视图菜单->上一张纹理 - 上一张纹理性能(也可点击左箭头)。
- 对象菜单->默认 - 原始
ImgTexture
性能。 - 对象菜单-><对象名称> - 将
Quadric
的形状更改为所选对象。 - 对象菜单->下一对象 - 下一个对象性能(也可点击空格键)。
- 对象菜单->上一对象 - 上一个对象性能(也可点击退格键)。
- 向上箭头 - 将对象移远。
- 向下箭头 - 将对象移近。
- 按住鼠标左键移动鼠标 - 绕垂直和水平轴旋转对象。
构建说明
解决方案配置必须设置为Release,平台设置为Win32。
提供的项目是使用MSVS-2010的工具通过MSVS-2015 pro开发的。因此,exe文件即使对Windows-XP也有效。如果您不需要在Windows-XP中运行该应用程序,只需将工具更改为MSVS-2015。
默认编码属性为 UNICODE;尽管 MBCS 编码也可用,只需更改属性即可。
即使您是第一次使用MSVS,只需选择菜单调试->开始执行(不调试),程序GLPlane.exe应该会开始构建并运行。
项目源代码和数据存储。
GLPlaneproj
路径下的标准源代码是使用标准的MFC应用程序向导创建的。
- GLPlane.cpp - 定义应用程序的标准类行为。
- MainFrm.cpp - 标准
CMainFrame
类的实现。 - CGLPlaneView.cpp - 标准
CView
类的实现;由作者使用标准的MFC应用程序向导程序创建的消息处理程序。 - GLPlane.rc 和 resource.h - 由作者使用标准的资源向导程序创建的菜单、对话框、加速器资源。
GLPlaneProj\GlobUse路径下的特殊源代码由作者在标准的OpenGL和几何例程的基础上开发。
- GlobTexture.cpp - 用于GL纹理过程(代替原始项目中使用的以前的Glaux.lib库)。
- Material.cpp - 对象的颜色性能。
- Vector_3D.cpp、Plane.cpp、PlaneArea.cpp、PlaneObject.cpp - 3D对象处理。
- Vector_2D.cpp, Poligon_2D.cpp - 2D 对象处理
代码说明。
保留了原始标准Cube Sample和以前ImgTexture
项目的所有属性。
所有以下菜单和加速键命令均使用标准的MFC AppWizard技术实现。
1. 初始化应用程序。
原始的Init()
过程及其相关过程与原始标准Cube Sample保持不变;在演示目的的CGLPlaneView::OnCreate
过程中,根目录Data文件夹中的图像文件的纹理实现被放置在五行中。
int CGLPlaneView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
Init(); // initialize OpenGL borrowed from Standard Cube Sample
m_globTexture.Add((WORD)LoadGLPlane(_T("Data/MSUN.jpg"))); //you may insert
//here any image file
m_globTexture.Add((WORD)LoadGLPlane(_T("Data/famrt.jpg")));//from any pathway valid
m_globTexture.Add((WORD)LoadGLPlane(_T("Data/vldv.jpg"))); //and as much as you like
m_globTexture.Add((WORD)LoadGLPlane(_T("Data/rsfsr.jpg")));
m_globTexture.Add((WORD)LoadGLPlane(_T("Data/ussr.jpg")));
m_pPlaneObj = new CPlaneObject; //Plane Object Initialization
return 0;
}
在这里,您可以插入来自任何有效路径的任意数量的图像文件。
与以前的ImgTexture
项目相比,唯一的区别是CPlaneObject
的初始化。
2. 绘制GL场景。
由虚拟过程OnDraw
调用的OpenGL绘图过程。
void CGLPlaneView::OnDraw(CDC* /*pDC*/)
{
DrawScene();
}
void CGLPlaneView::DrawScene(void)
{
static BOOL bBusy = FALSE; //Draw Scene busy flag
if(bBusy) //If Drawing going on return
return;
bBusy = TRUE; //Draw Scene busy flag on
glClearDepth(1.0f); //Specifies the clear value
//for the depth buffer
glClearColor(0.9f, 0.9f, 0.9f, 1.0f); //Set Background Color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //Indicate the buffers to be cleared
glPushMatrix(); //push the current matrix stack
glTranslatef(0.0f, 0.0f, -m_z); //move object far-near
glRotatef(wAngleX, 1.0f, 0.0f, 0.0f); //rotate object
glRotatef(wAngleY, 0.0f, 1.0f, 0.0f); //around the axe
glRotatef(wAngleZ, 0.0f, 0.0f, 1.0f); //specified
if(!m_bTexture) //if Texture flag off
switch (m_idObj) //depends on object style
{
case ID_DEFAULT:DefaultDemoCube();break; //default Cube Sample performance
default:
glEnable(GL_LIGHTING); //enable material properties
m_pPlaneObj->DrawPlaneObject(FALSE); //Draw Plane object
glDisable(GL_LIGHTING); //disable material properties
break;
}//switch (m_idObj)
else //if Texture flag On
{
glEnable(GL_TEXTURE_2D); //install current texture
glBindTexture(GL_TEXTURE_2D, (GLuint)m_globTexture.GetAt(m_texNum));
switch (m_idObj) //depends on object style
{
case ID_DEFAULT:
glDisable(GL_LIGHTING);
DefaultTextureCube(); //Texture Cube ImgTexture performance
break;
default:m_pPlaneObj->DrawPlaneObject(TRUE); break;//Draw Plane object
}//switch (m_idObj)
glDisable(GL_TEXTURE_2D);
}//if(m_bTexture)
glPopMatrix(); //Pop the current matrix stack
glFinish(); //Blocks until all OpenGL execution is complete
SwapBuffers(wglGetCurrentDC()); //Exchanges the front and back buffers
bBusy = FALSE; //Draw Scene busy flag off
}
在DrawScene
过程中,唯一有趣的点是DrawPlaneObject
过程(其他所有都是OpenGL例程,可以在我以前的CodeProject文章《50个OpenGL MFC项目合集》的项目中找到)。
void CPlaneObject::DrawPlaneObject(BOOL bTexture )
{
for(POSITION pos = m_planeList.GetHeadPosition(); pos != NULL;)
{
CPlaneArea * pp = (CPlaneArea *)m_planeList.GetNext(pos);
pp->DrawPlaneArea (bTexture);
}//for(POSITION pos = m_planeList.GetHeadPosition(); pos != NULL;)
}
核心思想是CPlaneObject
包含对象列表,其中有CPlaneArea
面以及用于绘制的DrawPlaneArea
过程。
void CPlaneArea ::DrawPlaneArea(BOOL bTexture)
{
if(!m_pointList.GetCount()) //if no points do nothing
return;
POSITION psn = NULL; //Position in m_texturePointList
if (m_texturePointList.GetCount() && bTexture)//if has texture points list and texture flag
{
glEnable(GL_TEXTURE_2D); //enable texturing
glDisable(GL_LIGHTING); //disable material properties
psn = m_texturePointList.GetHeadPosition(); //Position in m_texturePointList initialize
if (m_myTextureNum >= 0) //If the Face has own texture
if (m_myTextureNum != m_texNum) //install Face own texture
glBindTexture(GL_TEXTURE_2D, (GLuint)m_globTexture.GetAt(m_myTextureNum));
}
else //if no texture points in list or no texture flag
{
glDisable(GL_TEXTURE_2D); //disable texturing
glEnable(GL_LIGHTING); //enable material properties
material.SetMaterial(GL_FRONT); //install Face's own material
}
::glEnable(GL_AUTO_NORMAL); //enable normalization
::glNormal3f(m_n.x, m_n.y, m_n.z); //set Face's own normal
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); //start Polygon Draw
glBegin(GL_POLYGON);
for ( POSITION pos = m_pointList.GetHeadPosition(); pos != NULL;)
{
Vector_3D * pnt = (Vector_3D *)m_pointList.GetNext(pos);//Every point of the Face
if (m_texturePointList.GetCount()&& bTexture)//if has texture points in list
//and texture flag
if(psn != NULL) //Position in m_texturePointList valid
{
//Every point of the m_texturePointList:
Vector_2D * ptx = (Vector_2D *)m_texturePointList.GetNext(psn);
glTexCoord2f((float)ptx->x, (float)ptx->y);//bind point corresponding in texture
}
//Draw current point of the face:
::glVertex3f( (GLfloat) pnt->x, (GLfloat) pnt->y, (GLfloat) pnt->z );
}//for ( POSITION pos = m_pointList.GetHeadPosition(); pos != NULL;)
glEnd(); //complete Polygon Draw
if (m_texturePointList.GetCount() && bTexture)//if has texture points in list
//and texture flag
{
if (m_myTextureNum >= 0) //If the Face has own texture
if (m_myTextureNum != m_texNum) //restore Global texture
glBindTexture(GL_TEXTURE_2D, (GLuint)m_globTexture.GetAt(m_texNum));
}
}
class CPlaneArea:public CPlane
从3DCPlane
类派生。
class CPlane : public CObject
{
public:
CPlane();
CPlane(Vector_3D v0, Vector_3D v1, Vector_3D v2);//three points determine the Plane
virtual ~CPlane();
Vector_3D m_n; //normal to the Plane
float m_D; //distance from axes center point to the Plane
};
并包含以下关键特性描述。
CPlaneObject * m_pPlaneObject; //Reference to Parent Plane object
CObList m_pointList; //Object List of 3D points
CObList m_texturePointList; //Object List of 2D points bound to texture
int m_myTextureNum; //Planes' own texture
CMaterial material; //Planes' own material
3. 平面对象创建。
此项目中用于创建平面对象的标识符在PlaneObject.h中声明,如下所示:ID_BOX
、ID_SPHERE
、ID_CYLINDER
、ID_CONE
、ID_DISK
、ID_QUADPLANE
、ID_THORUS
、ID_TETRAHEDRON
、ID_CUBE
、ID_OCTOHEDRON
、ID_DODECA
、ID_ICOSAHEDRON
。
对于ID_BOX
或ID_QUADPLANE
,平面对象的初始化非常清晰:只需将点分配给相应的面。
对于ID_SPHERE
、ID_CYLINDER
、ID_CONE
、ID_DISK
或ID_THORUS
,平面对象的初始化是通过标准二次方程分配给每个面的。
但对于正多面体ID_TETRAHEDRON
、ID_CUBE
、ID_OCTOHEDRON
、ID_DODECA
、ID_ICOSAHEDRON
,我很自豪地展示一项绝活:通用多面体程序CPlaneObject::CreatePolyHedron
。
void CPlaneObject::CreatePolyHedron(int nType, double a)
{
double fi = 0.5 * (1 + sqrt(5.)); //special for ID_DODECA and ID_ICOSAHEDRON
double ksi = sqrt((5. - sqrt(5.))*0.5); //special for ID_DODECA
int nn = 3; //num of sides of the face
if (nType == ID_DODECA) nn = 5;
else if( nType == ID_CUBE) nn = 4;
Polygon_2D * pPl = new Polygon_2D;
//Create 2D polygon with equal sides a:
double m = pPl->CreateEquilateral(nn, a); //m - inradius of equilateral polygon
//default nType = ID_TETRAHEDRON expected:
double r = a * 0.5 / sqrt(6.); //inradius of the polyhedron
double tn2 = 1 / sqrt(2.); //tan of half angle between faces
switch (nType)
{
case ID_CUBE:
r = a*0.5;
tn2 = 1;
break;
case ID_OCTOHEDRON:
r = a*0.5*sqrt(2. / 3.);
tn2 = sqrt(2.);
break;
case ID_DODECA:
r = a * 0.5 * fi *fi / ksi;
tn2 = fi;
break;
case ID_ICOSAHEDRON:
r = a*0.5 * fi * fi / sqrt(3.);
tn2 = fi*fi;
break;
}
double tetta = 2.*atan(tn2); //angle between faces
//Create First face:
CPlaneArea * plNew = AppendPlaneArea();//Create 3D polygon from 2D:
plNew->CreateFromPolygon_2D(Vector_3D(0, 0, (float)-r),
Vector_3D(1, 0, 0), Vector_3D(0, 1, 0), pPl);
plNew->CreatePolyHedron(pPl, m, r, tetta); //Create next face using 2D polygon
//and m, r, tetta
SetClockwise();
int nCount = 0;
for (POSITION pos = m_planeList.GetHeadPosition(); pos != NULL; nCount++)
{
CPlaneArea * ppl = (CPlaneArea *)m_planeList.GetNext(pos);
//every face obtains its' own texture from textures' list:
ppl->m_myTextureNum = nCount % m_globTexture.GetSize();
}//for (POSITION pos = m_planeList.GetHeadPosition(); pos != NULL;)
}
CPlaneObject::CreatePolyHedron
创建第一个面,并由CPlaneArea::CreatePolyHedron
程序从一个面创建所有其他面。
void CPlaneArea::CreatePolyHedron(Polygon_2D * pPl, double m, double r, double tetta)
{
for (POSITION pos = m_pointList.GetHeadPosition(); pos != NULL;)
{//for each point on a current face:
Vector_3D * pV = (Vector_3D *)m_pointList.GetNext(pos);
if (GetNeigbourOfLineSegment(pV) != NULL) //if the polygon side is already common
continue; //with another polygon do nothing
Vector_3D * pVn = GetNextPoint(pV, TRUE);
Vector_3D rzn = (*pVn - *pV); //Vector from current point to the nect one
Vector_3D ex = Normalize(rzn); //make unit vector
Vector_3D ny = m_n^ex; //x unit vector of new face
Vector_3D ey = m_n * (float)sin(tetta) + ny *(float) cos(tetta);//y unit vector of new face
CPlaneArea * plt = m_pPlaneObject->AppendPlaneArea();
Vector_3D vc((*pV + *pVn)* 0.5f + (float)m * ey); //center of the new face
if (fabs(!vc - r) < GeomTreshold)
plt->CreateFromPolygon_2D(vc, ex, ey, pPl);
else
{
ey = m_n * (float)sin(tetta) - ny * (float)cos(tetta);
vc = Vector_3D((*pV + *pVn)* 0.5f - (float)m * ey);
plt->CreateFromPolygon_2D(vc, Vector_3D(0) - ex, Vector_3D(0) - ey, pPl);
}
plt->CreatePolyHedron(pPl, m, r, tetta);//Create next face using 2D polygon
//pPl and m, r, tetta
}
//Create texture points for the current face :
for (POSITION pos = pPl->m_PointList.GetHeadPosition(); pos != NULL;)
{
Vector_2D * pV = (Vector_2D *)pPl->m_PointList.GetNext(pos);
Vector_2D vv((*pV)*0.5 / m);
Vector_2D * pt = new Vector_2D(vv + Vector_2D(0.5));
m_texturePointList.AddTail(pt);
}
}
正多面体的构造原理如下图所示。
CPlaneObject::CreatePolyHedron
程序创建第一个等边多边形(蓝色),并计算多边形的内切圆半径和面之间的角度。CPlaneArea::CreatePolyHedron
程序使用蓝色多边形,利用多边形的内切圆半径和面之间的角度创建下一个等边多边形(红色)。CPlaneArea::CreatePolyHedron
程序使用红色多边形,利用多边形的内切圆半径和面之间的角度创建下一个等边多边形(绿色)。CPlaneArea::CreatePolyHedron
程序继续进行,直到创建完正多面体的所有面。
使用提供的项目开发您自己的应用程序。
您可以采用整个项目,将其重命名为我以前CodeProject文章《一键从现有代码创建MFC项目》的项目,并随意组合和改进代码。
或者,您可以从本项目中提取GlobUse目录,并将其中包含的特殊过程包含到您自己的任何OpenGL项目中,通过菜单项目->现有项。
如果您引用我的代码,将不胜感激。
关注点
您可以将提供的GLPlane.exe与我以前CodeProject文章《50个OpenGL MFC项目合集》中的第18课示例“Quadratic”进行比较,其中演示了“真正”的二次曲面表示。
提供的半二次曲面对象在技术上易于维护,因为它们在创建时就“知道”它们代表哪个二次曲面。创建后,它们就成为多边形的对象列表,易于处理:剪切、删除、分割、移动、放大、缩小、旋转——所有您想通过您的开放代码进行的操作。我希望在我未来的文章中对此进行演示。
CPlaneArea
类中的对象列表m_texturePointList
是您自己进行纹理显示的工具。
该项目是在MFC平台上开发的。尽管如此,在MFC中开发的所有内容都可以转换为Win32,反之亦然。
历史
我以前所有的CodeProject文章都是本文的前身。
我相信本文是我接下来即将发表的文章的前奏。
工作将继续进行...