初识标签云是在去年年末,一看到这个应用我就特别感兴趣。还记得08年在北京做Mobile应用的时候就在想,是不是可以通过某种方式做界面扩展,让相对狭小的手机屏幕只显示有效部分,而扩展部分可以在屏幕以外,通过某种方式将他们调到屏幕以内。后来证实做这种思考的人不只是我自己,因为现在划屏应用已经是移动终端的基本应用模式了。当时也想过能不能在纵向做界面延伸,让界面深入到屏幕里面,要知道那可是无限的空间,不过没有想到什么好的方式。见到标签云的那一刻,我知道这就是我想要的,所以当时我就决定用OpenGL模拟一下,然后拿出来和大家共同探讨,看看有没有可能做成一个更成熟些应用。不过过年回来工作比较忙,所以也就搁置了。这两天刚阶段性的完成一个项目,所以得了空可以写这篇博客。
要实现功能就要先分析原理,通过使用和观察我觉得标签云的每一个标签都分布在一个球体上,球在视平面上的投影是一个圆,而整个球就根据鼠标和这个投影圆的位置关系不同而旋转。鼠标离圆心越远转速愉快,反之则越慢。球体旋转是围绕一根通过球心且与视平面平行的转轴进行的,鼠标和投影圆圆心的位置关系同样控制者转轴的方向,那就是转轴垂直于投影圆圆心和鼠标的连线。最后一点,旋转过程总是迎着鼠标进行。
知道了基本原理就要开始分析算法了,实现三维空间用OpenGL这自不必说,我曾经写过一篇日志《OpenGL控件》介绍了一个内置OpenGL基本框架环境的自定义控件,这次就是极基于这个控件的一个扩展应用。我还曾经在《三维向量类》一文中介绍过我自己写的一个进行三维程序设计时很实用的向量类CVector,当然这次建模还是要依靠它。唯一的问题是OpenGL对于中文支持的不好,有没有什么方法可以方便的显示中文呢?我这里采用的方法是在内存中生成一张文字的贴图,然后用Alpha混合的方式赋予一个plan,这样就可以利用系统字库在OpenGL中显示各种类型丰富的文字了。好了,主要问题都解决了,开始写代码。
首先定义一个结构体来代表标签元中的一个标签:

 typedef struct    tagTAGNODE{ CVector     m_vtrPos;   // 标签位置   float       m_fWidth,   // 标签长                m_fHeight;  // 标签宽    UINT        m_uTexture;     // 贴图号}CloudTag, *lpCloudTag;     

然后在COpenGLCtrl里维护一个CloudTag数组用来管理所有标签,也就是整个云。函数AddCloud用来添加一个标签,其代码如下:

 view plaincopyprint?void COpenGLCtrl::AddCloud(CString strName, COLORREF clrText, CString strFontName, int nPointSize, int nWeight, UINT uCharset)  {      int         nMode;      CSize       sizeText;      CRect       rectTag;      CBitmap     bitmapTemp, *pOldBitmap;      CFont       fontTag, *pOldFont;      COLORREF    clrOld;      LOGFONT     lf;      CDC*        pDC         = GetDC();      CDC*        pMemDC      = new CDC;      lpCloudTag  pCloudTag   = new CloudTag;      int         nExtend     = 5;      float       fTemp;  

    //设置字体       memset(&lf, 0, sizeof(LOGFONT));  

    //设置字体样式       wcscpy_s(lf.lfFaceName, strFontName);      lf.lfHeight  = nPointSize;      lf.lfWeight  = nWeight;      lf.lfCharSet = uCharset;  

    fontTag.CreateFontIndirect(&lf);  

    //创建位图内存       pMemDC->CreateCompatibleDC(pDC);      pOldFont    = pMemDC->SelectObject(&fontTag);      nMode       = pMemDC->SetBkMode(TRANSPARENT);      clrOld      = pMemDC->SetTextColor(clrText);  

    sizeText    = pMemDC->GetTextExtent(strName, strName.GetLength());      pCloudTag->m_fWidth      = float(sizeText.cx+nExtend)/40;      pCloudTag->m_fHeight = float(sizeText.cy+nExtend)/40;      rectTag.SetRect(0, 0, sizeText.cx+nExtend, sizeText.cy+nExtend);  

    bitmapTemp.CreateCompatibleBitmap(pDC, rectTag.Width(), rectTag.Height());      pOldBitmap  = pMemDC->SelectObject(&bitmapTemp);  

    //填充客户区       pMemDC->DrawText(strName, strName.GetLength(), rectTag, DT_SINGLELINE|DT_CENTER|DT_VCENTER);      pMemDC->SetBkMode(nMode);      pMemDC->SelectObject(pOldFont);      pMemDC->SelectObject(pOldBitmap);      pMemDC->SetTextColor(clrOld);      delete  pMemDC;  

    BITMAP      bmData;      bitmapTemp.GetBitmap(&bmData);      unsigned char*  pData   = new unsigned char[bmData.bmWidthBytes*bmData.bmHeight];      bitmapTemp.GetBitmapBits(bmData.bmWidthBytes*bmData.bmHeight, pData);      for(int i=0; i<bmData.bmWidth; i++)      {          for(int j=0; j<bmData.bmHeight; j++)          {              if(pData[i*4+j*bmData.bmWidthBytes]!=0 || pData[i*4+j*bmData.bmWidthBytes+1]!=0 || pData[i*4+j*bmData.bmWidthBytes+2]!=0)                  pData[i*4+j*bmData.bmWidthBytes+3]=255;          }      }      glGenTextures(1, &pCloudTag->m_uTexture);      glBindTexture(GL_TEXTURE_2D, pCloudTag->m_uTexture);      gluBuild2DMipmaps(GL_TEXTURE_2D, 4, bmData.bmWidth, bmData.bmHeight, GL_RGBA, GL_UNSIGNED_BYTE, pData);  

    delete  pData;  

    fTemp   = (rand()%50)/10.0f+3;      pCloudTag->m_vtrPos  = CVector(0, 0, fTemp);      fTemp   = (rand()%360-180)/360.0f;      pCloudTag->m_vtrPos.Rotate(fTemp, CVector(1, 0, 0));      fTemp   = (rand()%360-180)/360.0f;      pCloudTag->m_vtrPos.Rotate(fTemp, CVector(0, 1, 0));      fTemp   = (rand()%360-180)/360.0f;      pCloudTag->m_vtrPos.Rotate(fTemp, CVector(0, 0, 1));      m_tcDemo.Add(pCloudTag);  }  

void COpenGLCtrl::AddCloud(CString strName, COLORREF clrText, CString strFontName, int nPointSize, int nWeight, UINT uCharset){ int         nMode;    CSize       sizeText; CRect       rectTag;  CBitmap     bitmapTemp, *pOldBitmap;  CFont       fontTag, *pOldFont;   COLORREF    clrOld;   LOGFONT     lf;   CDC*        pDC         = GetDC();   CDC*        pMemDC      = new CDC;   lpCloudTag  pCloudTag   = new CloudTag;  int         nExtend     = 5; float       fTemp;

    //设置字体    memset(&lf, 0, sizeof(LOGFONT));

    //设置字体样式  wcscpy_s(lf.lfFaceName, strFontName); lf.lfHeight  = nPointSize;   lf.lfWeight  = nWeight;  lf.lfCharSet = uCharset;

   fontTag.CreateFontIndirect(&lf);

    //创建位图内存  pMemDC->CreateCompatibleDC(pDC);   pOldFont    = pMemDC->SelectObject(&fontTag); nMode       = pMemDC->SetBkMode(TRANSPARENT); clrOld      = pMemDC->SetTextColor(clrText);

    sizeText    = pMemDC->GetTextExtent(strName, strName.GetLength());    pCloudTag->m_fWidth      = float(sizeText.cx+nExtend)/40;    pCloudTag->m_fHeight = float(sizeText.cy+nExtend)/40;    rectTag.SetRect(0, 0, sizeText.cx+nExtend, sizeText.cy+nExtend);

  bitmapTemp.CreateCompatibleBitmap(pDC, rectTag.Width(), rectTag.Height());    pOldBitmap  = pMemDC->SelectObject(&bitmapTemp);

    //填充客户区   pMemDC->DrawText(strName, strName.GetLength(), rectTag, DT_SINGLELINE|DT_CENTER|DT_VCENTER);   pMemDC->SetBkMode(nMode);  pMemDC->SelectObject(pOldFont);    pMemDC->SelectObject(pOldBitmap);  pMemDC->SetTextColor(clrOld);  delete  pMemDC;

 BITMAP      bmData;   bitmapTemp.GetBitmap(&bmData);    unsigned char*  pData   = new unsigned char[bmData.bmWidthBytes*bmData.bmHeight];    bitmapTemp.GetBitmapBits(bmData.bmWidthBytes*bmData.bmHeight, pData); for(int i=0; i<bmData.bmWidth; i++) {     for(int j=0; j<bmData.bmHeight; j++)        {         if(pData[i*4+j*bmData.bmWidthBytes]!=0 || pData[i*4+j*bmData.bmWidthBytes+1]!=0 || pData[i*4+j*bmData.bmWidthBytes+2]!=0)             pData[i*4+j*bmData.bmWidthBytes+3]=255;        } } glGenTextures(1, &pCloudTag->m_uTexture);  glBindTexture(GL_TEXTURE_2D, pCloudTag->m_uTexture);   gluBuild2DMipmaps(GL_TEXTURE_2D, 4, bmData.bmWidth, bmData.bmHeight, GL_RGBA, GL_UNSIGNED_BYTE, pData);

 delete  pData;

  fTemp   = (rand()%50)/10.0f+3;  pCloudTag->m_vtrPos  = CVector(0, 0, fTemp);  fTemp   = (rand()%360-180)/360.0f;   pCloudTag->m_vtrPos.Rotate(fTemp, CVector(1, 0, 0));   fTemp   = (rand()%360-180)/360.0f;   pCloudTag->m_vtrPos.Rotate(fTemp, CVector(0, 1, 0));   fTemp   = (rand()%360-180)/360.0f;   pCloudTag->m_vtrPos.Rotate(fTemp, CVector(0, 0, 1));   m_tcDemo.Add(pCloudTag);

这个函数是生成标签的核心函数,在程序里标签表现出来的形式就是文本,所以函数支持对文本的几乎一切定制,这包括内容、颜色、字体、字号、样式等。函数首先会创建一个字体,然后根据文本内容需要的大小创建一个内存位图,将文字绘制于这个位图之上。准备好文字的位图就要通过它来制作材质了,这里有一个问题,位图是没有Alpha通道的,怎么办呢,有没有什么相对简单的方法处理呢?这里我的处理方法是这样的,看代码,通过GetBitmap获取位图信息然后观察bmData.bmWidth和bmData.bmWidthBytes的值,可以发现bmWidthBytes是bmWidth的四倍。原来位图在内存里是以RGBA的方式存储的,所以通过GetBitmapBits获取到的数据就是4位的。众所周知,内存位图创建时是黑色的,这里我认定标签文字不能为黑色,所以检测数据如果是黑色的就把Alpha设为透明,不是就设为不透明,然后创建贴图。最后设置向量,在一个范围内随机生成一个长度做一个指向屏幕外的向量,在以x、y、z三个轴随机旋转一个角度,这样就完成了一个标签的建模。
渲染的部分很简单,就是常规的OpenGL绘制,只不过加上了材质设置了Alpha透明。其实这个Demo做的比较粗糙,文字周围存在黑边,不过做这个例子不是特别追求表现所以也就没有特别处理,代码如下:

 view plaincopyprint?void COpenGLCtrl::OnPaint()   {      CPaintDC    dc(this);  

    //绘背景色       COLORREF    clrBkgnd    = GetSysColor(COLOR_BTNFACE);      glClearColor(float(GetRValue(clrBkgnd))/255, float(GetGValue(clrBkgnd))/255, float(GetBValue(clrBkgnd))/255, 0.0f);      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  

    //设置场景坐标系       glLoadIdentity();      //设置观察点       gluLookAt(0, 0, 30, 0, 0, 0, 0, 1, 0);  

    INT_PTR nCount  = m_tcDemo.GetCount();      for(int i=0; i<nCount; i++)      {          lpCloudTag  pCloudTag   = m_tcDemo.GetAt(i);  

        glBindTexture(GL_TEXTURE_2D, pCloudTag->m_uTexture);          glEnable(GL_BLEND);          glEnable(GL_ALPHA_TEST);          glAlphaFunc(GL_GREATER, 0.1f);          glBegin(GL_QUADS);          glTexCoord2i(0, 0), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX-pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY+pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);          glTexCoord2i(1, 0), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX+pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY+pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);          glTexCoord2i(1, 1), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX+pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY-pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);          glTexCoord2i(0, 1), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX-pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY-pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);          glEnd();          glDisable(GL_ALPHA_TEST);          glDisable(GL_BLEND);  

    }  

    //翻页       SwapBuffers(m_pDC->m_hDC);  }  

void COpenGLCtrl::OnPaint() {  CPaintDC    dc(this);

   //绘背景色    COLORREF    clrBkgnd    = GetSysColor(COLOR_BTNFACE);    glClearColor(float(GetRValue(clrBkgnd))/255, float(GetGValue(clrBkgnd))/255, float(GetBValue(clrBkgnd))/255, 0.0f);   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 //设置场景坐标系 glLoadIdentity(); //设置观察点   gluLookAt(0, 0, 30, 0, 0, 0, 0, 1, 0);

  INT_PTR nCount  = m_tcDemo.GetCount();   for(int i=0; i<nCount; i++) {     lpCloudTag  pCloudTag   = m_tcDemo.GetAt(i);

       glBindTexture(GL_TEXTURE_2D, pCloudTag->m_uTexture);       glEnable(GL_BLEND);       glEnable(GL_ALPHA_TEST);      glAlphaFunc(GL_GREATER, 0.1f);        glBegin(GL_QUADS);        glTexCoord2i(0, 0), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX-pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY+pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);      glTexCoord2i(1, 0), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX+pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY+pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);     glTexCoord2i(1, 1), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX+pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY-pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);      glTexCoord2i(0, 1), glVertex3f(pCloudTag->m_vtrPos.m_fVectorX-pCloudTag->m_fWidth/2, pCloudTag->m_vtrPos.m_fVectorY-pCloudTag->m_fHeight/2, pCloudTag->m_vtrPos.m_fVectorZ);       glEnd();      glDisable(GL_ALPHA_TEST);     glDisable(GL_BLEND);

    }

   //翻页  SwapBuffers(m_pDC->m_hDC);}   

剩下的事情就是鼠标控制和动画实现了,由于我的向量类很好的支持了旋转功能,而且标签云建模就是通过向量实现的,所以这一步也就好说了。所有标签都是通过以原点为起点的向量定位的,所以转轴也是通过原点的在XOZ面上的向量。在OnMouseHover中计算状态和转速,然后在OnTimer里旋转每个标签的向量就是了,代码如下:

view plaincopyprint?LRESULT COpenGLCtrl::OnMouseHover(WPARAM wParam, LPARAM lParam)  {      CRect   rectView;      CPoint  point;  

    GetWindowRect(rectView);      GetCursorPos(&point);  

    m_vtrRotate.SetVector(float(-point.x), float(point.y), 0, -float(rectView.right+rectView.left)/2, float(rectView.bottom+rectView.top)/2, 0);      m_vtrRotate.Rotate(-PI/2, CVector(0, 0, 1));  

    m_fAngle    = m_vtrRotate.GetMod()/2000;      return 1;  }  

LRESULT COpenGLCtrl::OnMouseHover(WPARAM wParam, LPARAM lParam){    CRect   rectView; CPoint  point;

  GetWindowRect(rectView);  GetCursorPos(&point);

   m_vtrRotate.SetVector(float(-point.x), float(point.y), 0, -float(rectView.right+rectView.left)/2, float(rectView.bottom+rectView.top)/2, 0);    m_vtrRotate.Rotate(-PI/2, CVector(0, 0, 1));

    m_fAngle    = m_vtrRotate.GetMod()/2000; return 1;}[cpp] view plaincopyprint?void COpenGLCtrl::OnTimer(UINT_PTR nIDEvent)  {      switch(nIDEvent)      {      case TIMER_MOVE:          {              if(m_fAngle==0)                  return;  

            INT_PTR nCount  = m_tcDemo.GetCount();              for(int i=0; i<nCount; i++)              {                  lpCloudTag  pCloudTag   = m_tcDemo.GetAt(i);                  pCloudTag->m_vtrPos.Rotate(m_fAngle, m_vtrRotate);                  Invalidate();              }          }break;      }  

    CWnd::OnTimer(nIDEvent);  }  

void COpenGLCtrl::OnTimer(UINT_PTR nIDEvent){    switch(nIDEvent)  { case TIMER_MOVE:      {         if(m_fAngle==0)             return;

         INT_PTR nCount  = m_tcDemo.GetCount();           for(int i=0; i<nCount; i++)         {             lpCloudTag  pCloudTag   = m_tcDemo.GetAt(i);             pCloudTag->m_vtrPos.Rotate(m_fAngle, m_vtrRotate);             Invalidate();         }     }break;   }

CWnd::OnTimer(nIDEvent);
} 到此基本的功能就已经全部实现了,在我的资源里上传了这个Demo有兴趣朋友可以下载研究一下,有什么好的想法可以和我进一步交流。做这个东西,写这篇文章权当抛砖引玉,希望能给大家些灵感,从三维的角度作出有更好用户体验的界面设计。最后贴一个效果图,见笑见笑。
[img][/img]

转(http://blog.csdn.net/xianglitian/article/details/6590687)

MFC OpenGL标签云 (转)相关推荐

  1. flash特效原理:标签云

    其实标签云是一个比较常见的特效类,在wondefl里面已经被做烂了,经典之余,涉及到知识点,是基于球体和旋转方面的数学应用,在这方面而言,标签云的制作并不是十分难,多少会有很多的意思,不妨尝试制作一下 ...

  2. flash特效原理 标签云

    其实标签云是一个比较常见的特效类,在wondefl里面已经被做烂了,经典之余,涉及到知识点,是基于球体和旋转方面的数学应用,在这方面而言,标签云的制作并不是十分难,多少会有很多的意思,不妨尝试制作一下 ...

  3. Python pytagcloud 中文分词 生成标签云 系列(一)

    转载地址:https://zhuanlan.zhihu.com/p/20432734 工具 Python 2.7 (前几天试了试 Scrapy 所以用的 py2 .血泪的教训告诉我们能用 py3 千万 ...

  4. Python jquery标签云

    很多知名博主都喜欢弄个标签云.今天特地看了下源码.把标签云的方法单独扣了出来.这里做一下记录 html页面代码 <!DOCTYPE html PUBLIC "-//W3C//DTD X ...

  5. 基于纯 CSS3 技术实现美观的标签云效果

    标签云是博客的标配功能,能够清晰的呈现博客的各个关键词和主题.在这个效果中,您将学习如何使用 CSS3 技术创建一个效果精美的标签云效果. 作为实验项目,使用了 CSS3 渐变,阴影和最重要的的 CS ...

  6. python 标签云_标签云算法Python实现

    标签云(Tag Cloud)常见于各种博客站点中,标签有利于网站内容分类,还可以用于相关性内容推荐.近日笔者有空把个人的开源博客Django_blog添加了一个新功能--标签云.最终效果请访问:htt ...

  7. [HTML5]3D标签云

    index.html <html> <head> <meta http-equiv="Content-Type" content="text ...

  8. python标签使用教程_怎样用Python做标签云

    怎样用Python做标签云 标签云是比较直观的频率分布表现方式,很多网站和APP在年度盘点和总结时会使用.Python生成标签云有一个比较易用的库 pytagcloud. Python做标签云的具体方 ...

  9. 仅使用HTML和CSS实现的标签云效果

    标签云的效果在博客和网站上不难见到,它其实就是带有超链接的某些关键字,为了达到强调主题的作用.通常出现概率比较大或者受欢迎的标签文字显示比较大,相反的就显示的小. 来源于TagCrowd.com 我们 ...

最新文章

  1. 快速提升性能的SQL语句,建议收藏
  2. (一)安装docker
  3. Tesla超越谷歌无人驾驶汽车雄心的背后,是以色列公司Mobileye
  4. eclipse分级,分级列表显示 - bieshixuan的个人博客 - OSCHINA - 中文开源技术交流社区...
  5. mysql设置最大查询时间_mysql如何限制sql查询时间
  6. JAVA的日期与毫秒的相互转换
  7. 二叉树最大深度(LeetCode 104)
  8. 概要设计的作用_【教育年会与颁奖典礼】第六届中国人居环境设计学年奖
  9. c 显示服务器相关的图片到对话框,使用MFC CImage类绘制PNG图片时遇到的问题
  10. 单盘齿廓测量仪的设计
  11. c语言二次方程的实根,C程序求二次方程的根
  12. angular $element.html 数据展示不出来,element-ui Table 组件循环列表数据不显示?
  13. java实现用用户头像代替小程序码的logo(任意图片替换小程序码logo)
  14. Modifiers should be declared in the correct order 修饰符应按正确的顺序声明
  15. BUUCTF-MRCTF2020
  16. JAVA——基于控制台的购书系统
  17. 搭建一个PHP后台的开发环境你所需要配置东西都有哪些?XAMPP问题
  18. 细谈网络同步在游戏历史中的发展变化(中)
  19. 爬虫入门,了解爬虫机制
  20. 正向动力学与反向动力学

热门文章

  1. tkinter之scale(我们暂且把scale叫做“测量器”吧)
  2. 【转】android call(mo)流程介绍,【转】超详细找call写call教程
  3. ATmega8,ATmega16,最小系统板,第一个好评来啦!
  4. 研究生期间自学模拟ic能在校招找到工作吗?
  5. FPGA DDR3带宽测试
  6. nodejs爬虫——汽车之家所有车型数据
  7. Linker Error、MRC与ARC、导航条背景
  8. linux播放器安卓系统下载软件,linux系统如何安装在线播放器和QQ?等常用软件?
  9. 网页视频播放器可兼容IE8
  10. 关于火狐浏览器下载文件时,中文文件乱码问题的处理方案。