zoukankan      html  css  js  c++  java
  • GIS三维地景仿真设计之地景的数学处理

    GIS三维地景仿真设计之地景的数学处理

    正文: 摘要:本文是《基于DEM数字高程模型和OpenGL的三维地景仿真处理》系列文章中的第四篇,主要对重绘函数的实现、地景模型的几何变换、高差缩放和网格缩放等内容进行介绍。

      关键字:DEM数字高程模型;OpenGL;地景仿真;GIS;VC++

      引言

      在上一篇文章中实现了对数字高程模型(DEM)的建模与场景绘制。本文将在此基础上为其添加人机交互功能,使用户能够随心所欲的设置对地景的观察角度、位置、远近以及对地景高差的缩放等。下面将要给出具体的实现过程:

      重绘函数的实现

      在本系列的前几篇文章中曾多次提到对ReDraw()函数的调用。该函数用来负责对场景的重绘。通常的做法是先调用glClear()和glLoadIdentity()函数完成屏幕的清空和对矩阵的单位矩阵重置,随后调用glPushMatrix()进行压栈并可在之后执行对场景的旋转、平移、缩放、绘制列表的执行等操作直到glPopMatrix()函数被调用。在glPopMatrix()出栈之后需要通过glFlush()函数强制绘图的完成。由于采取的是双缓存,还需要通过SwapBuffers()交换缓存到当前正在执行的DC上去,这样的处理方式要比单缓存的绘图方式有很好的效果改善。下面给出ReDraw()函数的主要实现代码:

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清屏
    glLoadIdentity(); // 将矩阵清为单位矩阵
    glPushMatrix(); // 压栈
    glTranslatef(0.0, 0.0, -5000.0); // 平移到屏幕中心
    glRotatef(0.0f, 1.0f, 0.0f, 0.0f); // 旋转
    glRotatef(m_fxAngle, 1.0f, 0.0f, 0.0f);
    glRotatef(m_fzAngle, 0.0f, 0.0f, 1.0f);
    glTranslatef(m_fXOff, m_fYOff, m_fZOff); // 平移
    glScalef(m_fScale, m_fScale, m_fScale); // 缩放
    if (m_nViewMode == 0 || m_bTexture == FALSE) // 执行显示列表
    glCallList(Terrain);
    glPopMatrix(); // 出栈
    glFlush(); // 强制绘图完成
    SwapBuffers(wglGetCurrentDC()); // 交换缓存


      其中,glTranslatef()、glRotatef()、glScalef()等函数用以完成对场景的平移、旋转与缩放等几何变换操作。与之类似的还有glTranslated()、glRotated()、glScaled()等,其函数原型分别为:

    void glTranslated(GLdouble x, GLdouble y, GLdouble z );
    void glTranslatef(GLfloat x, GLfloat y, GLfloat z );
    void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z);
    void glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
    void glScaled(GLdouble x, GLdouble y, GLdouble z);
    void glScalef(GLfloat x, GLfloat y, GLfloat z);


      开始两个函数将目标分别沿X、Y、Z轴平移x、y、z。中间两个函数将目标分别以X、Y、Z轴为轴逆时针旋转x、y、z。最后两个函数将目标分别在X、Y、Z方向上缩放,缩放因子分别为x、y、z。通过改变这几个函数的参数传递将能够很容易的实现用户对地景的平移、旋转与缩放操作。

      对地景模型的平移、旋转与缩放处理

      用户对地景模型的控制不外呼通过键盘与鼠标来完成。为响应键盘操作,可在WM_KEYDOWN消息响应函数中添加如下的判断处理代码:

    switch(nChar) { // 漫游控制
     case VK_UP:
      m_fYOff += 20; break;
     case VK_DOWN:
      m_fYOff -= 20; break;
     case VK_LEFT:
      m_fXOff -= 20; break;
     case VK_RIGHT:
      m_fXOff += 20; break;
     case 87: // W
      m_fxAngle++; break;
     case 83: // S
      m_fxAngle--; break;
     case 65: // A
      m_fzAngle++; break;
     case 68: // D
      m_fzAngle--; break;
     case 70: // F 前进
      m_fScale += 0.05f; break;
     case 66: // B 后退
      if (m_fScale > 0.05f)
       m_fScale -= 0.05f;
      break;
     default: break;
    }
    ReDraw(); // 重绘场景


      其中,四个方向键将会相应的改变m_fXOff或m_fYOff的值,在ReDraw()执行场景重绘时改变后的参数将传递给glTranslatef()并被执行,从而完成对场景的平移。"W"、"S"、"A"、"D"四键与m_fxAngle和m_fzAngle等表示在各方向上旋转角度的变量相关,在ReDraw()执行场景重绘时这几个参数将传递到glRotatef ()并被执行,完成对场景的旋转。此外,还定义了"F"与"B"键,通过对glScalef ()的传入参数m_fScale的取值设置而实现对场景的缩放控制。

      与键盘控制相比,对鼠标控制的响应略显复杂一些。按照通常的使用习惯,应当在鼠标左键按下时开始启动对场景的拖动,直至鼠标左键的释放为止。在此期间,只要鼠标移动,就应当随即执行对场景的重绘,这样才不会使鼠标的拖拽显的生涩。基于上述考虑,在鼠标左键按下发出WM_LBUTTONDOWN消息的响应函数中将控制变量m_LeftButtonDown置位,以开启拖动控制,同时以m_LeftDownPos保存当前的鼠标位置备用:

    m_LeftButtonDown = TRUE; // 开始鼠标漫游
    m_LeftDownPos = point;


      鼠标滑动过程中将不断发出WM_MOUSEMOVE消息,因为我们只需要在按下鼠标左键的同时拖动时执行相关处理,因此在其响应函数中需要首先判断m_LeftButtonDown的状态,如果允许执行拖动处理则将根据当前鼠标位置与先前保存到m_LeftDownPos的前一次鼠标位置计算出应当旋转的角度,并通过ReDraw()函数绘制出角度更改后的场景:

    if(m_LeftButtonDown) { // 正在鼠标漫游
     m_fzAngle -= (float)(m_LeftDownPos.x - point.x)/3.0f;
     m_fxAngle -= (float)(m_LeftDownPos.y - point.y)/3.0f;
     m_LeftDownPos = point;
     ReDraw(); // 重绘场景
    }


      一旦鼠标左键释放,WM_LBUTTONUP消息将会发出,在其消息响应函数中需要将控制变量m_LeftButtonDown复位,等待下一次的鼠标拖动:

    m_LeftButtonDown = FALSE; // 结束鼠标漫游
    点击放大此图片


      上图为上述代码实现的对同一地景模型的不同角度观察结果图。

     高差缩放与网格缩放处理

      并非所有的场景都是起伏较大的,对于那些起伏不是很明显的地形,建模出来后可能就是一块平板,为了能够清楚的看出其地形走势,需要通过高差缩放来放大其各网格节点间的高程差距。这部分功能实现起来非常简单,只需将m_pDemH指向的高程数据缓冲区中的数据取值做相应的缩放处理并在重新计算法向量和初始化列表后重绘场景即可:

    if (dlg.m_fValue > 0) {
     m_fHRate *= dlg.m_fValue;
     for (int i = 0; i < m_nHSize; i++)
      m_pDemH[i] = (int)(dlg.m_fValue * m_pDemH[i]);
    }else{
     for (int i = 0; i < m_nHSize; i++)
      m_pDemH[i] = (int)(m_pDemH[i] / m_fHRate);
     m_fHRate = 1.0f;
    }


      下图为高差缩放系数为1.5时的效果图:


      前面实现的缩放处理能够起到场景的放大缩小效果,但这是通过改变场景的缩放系数来实现的,即"视觉"上的缩放,由众多网格所组成的地景模型大小并没有任何变化。下面将通过改变网络间隔的方法来达到地景放大、缩小的效果。其实现原理是通过改变DEM网络间隔参数m_nInterval的取值来实现的,在更改之后需要相应的调整m_pDemX和m_pDemY中网格平面坐标的取值,并同样需要在重新计算法向量和初始化列表后重绘场景:

    // 加大网络间隔(缩小时除以2)
    m_nInterval *= 2;
    DemHeader.interval = (float)m_nInterval;
    for (int i = 0; i < m_nDemY; i++) {
     for (int j = 0; j < m_nDemX; j++) {
      m_pDemX[m_nDemX * i + j] = m_nInterval * j - (m_nDemX / 2) * m_nInterval;
      m_pDemY[m_nDemX * i + j] = m_nInterval * i - (m_nDemY / 2) * m_nInterval;
     }
    }
    点击放大此图片


      上图为网格间隔倍增1倍时的场景。

      小结

      本文主要介绍了对已经绘制的DEM数字高程模型场景进行平移、旋转、缩放等几何变换处理方法以及对DEM数据的高差缩放和网格缩放等处理方法。读者应当能够掌握上述操作的基本实现过程。本文所述程序在Windows 2000 Professional + SP4下由Microsoft Visual C++ 6.0编译通过。


  • 相关阅读:
    SQL Server数据库查询区分大小写、全半角——排序规则的应用
    C#中查询字符串中是否包含指定字符/字符串,使用IndexOf还是Contains?
    【WM6.5】三星I8000按键码及窗体消息发送的方法备忘
    UoBlog 支持 MetaWeblog Api,可以使用 Windows Live Writer 离线发表日志
    C#中如何获取一个字符串的实际字符数
    使用HttpWebRequest发送HTTP请求,同时支持GET/POST方式提交。
    c#.NET中开发可用于Web网页的ActiveX控件
    CorePlex开发手记:一、Winform窗体皮肤及简单换肤机制
    .NET中简易实现线程安全
    在C#中截取指定长度的中文字符串(效率提高2500倍)
  • 原文地址:https://www.cnblogs.com/sunliming/p/1977110.html
Copyright © 2011-2022 走看看