65.9K
CodeProject 正在变化。 阅读更多。
Home

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

2017年11月20日

CPOL

6分钟阅读

viewsIcon

17748

downloadIcon

1197

在OpenGL MFC中,您可以根据自己的设计和通用多面体程序来实现替代的二次曲面。

引言

OpenGL中的二次曲面过程(gluSpheregluCilindergluCone等)是非常简单且有用的工具。但由于是闭源的,它们在纹理、颜色和形状表现方面存在限制和约束。有时,拥有一些具有可用代码的类似过程是值得的。

背景

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.dllmfc100u.dllmsvcp100.dllmsvcr100.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.rcresource.h - 由作者使用标准的资源向导程序创建的菜单、对话框、加速器资源。

GLPlaneProj\GlobUse路径下的特殊源代码由作者在标准的OpenGL和几何例程的基础上开发。

  • GlobTexture.cpp - 用于GL纹理过程(代替原始项目中使用的以前的Glaux.lib库)。
  • Material.cpp - 对象的颜色性能。
  • Vector_3D.cppPlane.cppPlaneArea.cppPlaneObject.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_BOXID_SPHEREID_CYLINDERID_CONEID_DISKID_QUADPLANEID_THORUSID_TETRAHEDRONID_CUBEID_OCTOHEDRONID_DODECAID_ICOSAHEDRON

对于ID_BOXID_QUADPLANE,平面对象的初始化非常清晰:只需将点分配给相应的面。

对于ID_SPHEREID_CYLINDERID_CONEID_DISKID_THORUS,平面对象的初始化是通过标准二次方程分配给每个面的。

但对于正多面体ID_TETRAHEDRONID_CUBEID_OCTOHEDRONID_DODECAID_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文章都是本文的前身。

我相信本文是我接下来即将发表的文章的前奏。

工作将继续进行...

© . All rights reserved.