zoukankan      html  css  js  c++  java
  • WorldWind学习系列十六:3D Cross Section插件功能分析——TerrainViewer

         很长时间没写WorldWind方面的东西啦!一方面是自己最近工作上忙点,一方面因为自己热情衰减了,俗话说,“一鼓作气,再而衰,三而竭”,我现在学习WW就有点没太有毅力和士气了!本来想这周末总结一下前段时间的WorldWind学习,没想自己放假期间自制力很差,没一点效率,几乎都上网玩了。

         WW的总结只能拖后了,可能过段时间有兴趣有时间了,可能会把总结写了,然后继续深入研究吧。我感觉自己学习或做事都缺点毅力,总是搞定虎头蛇尾的!本来研究WW好好的,可是看了.NET互操作方面的书感觉很好,于是兴致勃勃地学习.NET互操作。看了三章,遇到难点又想放弃来搞线程方面的。我有时都服了自己了:三心二意的!不扯周末的思想了。

         WW学习研究虽没像以前那样专注,但也还是时不时关注一下的,前段时间看到3D Cross Section插件,感觉很惊奇,就想研究一下,没想到自已一拖再拖,现在才准备写点东西。

         3D Cross Section插件主要是提取WW中当前视图的地形数据和影像数据,然后在新的窗口TerrainViewer中显示。也就是提取一部分三维在新的窗体里重点显示。功能就简单一说,3D Cross Section插件中实现提取WW数据的插件部分我们下次再说,我这次主要是关注TerrainViewer的实现。

                       

         TerrainViewer的功能可以单独使用,正如其名字就是一个简单的三维地形数据的浏览器,简直是Mini型的WW。但它里面内容很丰富,有很多知识点值得我们学习借鉴:一方面是C#知识;一方面是Direct3D方面知识;还有就是其中涉及数据算法方面的处理。

          首先,说一下其中的C#拖拽文件到窗体打开功能的实现,看过很多软件特别是视频播放器软件,只要将视频文件拖到上面就能播放该视频;看过Office软件普遍支持拖放打开相应的文件;看过只要将文件拖入回收站就能自己删除等等。这些拖拽方法是如何在C#实现的?自己搞编程很久了,没遇到过这样的需求,也没见过这样代码实现案例,所以自己也就没深入研究这方面的知识。在研究TerrainViewer功能时,看到支持拖拽功能,就首先学习了一下它是如何实现的。自己以后的程序支持类似的拖放打开文件功能多酷?!分析一下该功能代码,与大家分享一下。

                // Drag drop
                this.AllowDrop = true; //允许窗口拖放
                //注册拖放开始事件
                
    this.DragEnter += new DragEventHandler(this.OnDragEnter);
                //注册拖放处理事件
                
    this.DragDrop += new DragEventHandler(this.OnDragDrop);

     看看this.OnDragEnter和this.OnDragDrop事件处理中都分别做了什么。

            // File drop handling
            private  void  OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
            {
                e.Effect 
    = DragDropEffects.Copy;  // set the cursor to show a drop copy
            }

    上面的代码里主要是告诉,拖放的目的和效果是COPY。从下面的截图中,可以看到各种各种拖放效果。

         

     从MSDN上截取的DragDropEffects说明:

                    

    真正处理拖放文件的打开实现的代码:

    private  void  OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
            {
                
    string theFile;
                
    try
                {
                    
    // check to make sure the dropped item is of type FileDrop
                    if (e.Data.GetDataPresent(DataFormats.FileDrop))
                    {
                        //获取拖放数据
                        
    object filename = e.Data.GetData(DataFormats.FileDrop);
                        //这里是数组,说明支持多选文件的同时拖放
                        theFile 
    = (string)((System.Array)filename).GetValue(0);
                        
    // Create map from file
                        
    // MessageBox.Show("Dropped file : " + theFile);
                        //获取拖放文件的后缀名
                        string ext = Path.GetExtension(theFile);
                        
    string sky = skyFileName == null ? "" : skyFileName;
                        
    string tex = textureFileName == null ? "" : textureFileName;
                        
    if (!tex.StartsWith("colors")) tex = "colors/Geo_Water_1.png";
                        //根据不同的文件类型,分别处理
                        
    switch(ext) 
                        {
                            
    case ".jpg" :
                                
    break;
                            
    case ".png" :        // Load terrain from 8bit .png
                                DisposeMap();
                                terrainFileName 
    = theFile;
                                skyFileName 
    = sky;
                                textureFileName 
    = tex;
                                mapName 
    = terrainFileName;
                                mapSpan 
    = 0;
                                mapWidth 
    = 0;
                                dem16 
    = false;
                                verticalFactor 
    = 1.0f;
                                LoadMap();
                                
    break;
                            
    case ".bil" :        // Load terrain from 16bit SRTM binary .bil
                                DisposeMap();
                                terrainFileName 
    = theFile;
                                skyFileName 
    = sky;
                                textureFileName 
    = tex;
                                mapName 
    = terrainFileName;
                                mapSpan 
    = 0;
                                mapWidth 
    = 0;
                                dem16 
    = true;
                                verticalFactor 
    = 1.0f;
                                LoadMap();
                                
    break;
                            
    case ".xml" :        // Load map list from .xml
                                DisposeMap();
                                mapListFileName 
    = theFile;
                                InitializeMapList();        
    // Create Map menu from .xml
                                MapMenuSelectMap(0);        // Load first map
                                break;
                        }
                    }
                }
                
    catch (Exception ex)
                {
                    
    //MessageBox.Show(ex.Message.ToString());
                }
            }

     从上面分析可知,我们如果要在自己的程序中实现拖放功能,只需分别实现相应的自己的事件处理。大家也可以在网上搜搜相关资料,相信大致步骤是一样的。

           其次,说一下其中的键盘事件监听处理,其实在前面WW学习系列中已经分析了键盘监听处理。但是这次要分析的有的不同是,DirectX里面的键盘监听实现。该思路很新颖,自己之前没见过,在DirectX编程里可以借鉴一下。但不推荐使用,Form里的键盘监听处理已经很好很方便了,而且DirectX里键盘监听需要DirectX运行环境的。另外,除了新颖方面,我没看到该方法的优势。

            private Microsoft.DirectX.DirectInput.Device keyb;
            //完成输入设备(键盘)对象的初始化
            
    public void InitializeKeyboard()
            {

                
    keyb = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
                keyb.SetCooperativeLevel(
    this, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);
                keyb.Acquire();
            }

     键盘监听处理方法实现:

    DirectX键盘监听实现
    private void ReadKeyboard()
            {
                //获取键盘所以按键状态
               
     KeyboardState keys = keyb.GetCurrentKeyboardState();
                //通过
    keys[Key.LeftShift]方式获取SHIFT键是否按下
                bool shift = keys[Key.LeftShift]  || keys[Key.RightShift];
                
    bool ctrl = keys[Key.LeftControl]  || keys[Key.RightControl];
                
    double moveFactor = dist * 0.01f;
                
    // Toggle Light
                if (keys[Key.L] && !shift && !ctrl)    
                {
                    showLight 
    = true;
                    menuItemShowLight.Checked 
    = showLight;
                    redraw 
    = true;
                }
                
    if (keys[Key.L] && shift && !ctrl)    
                {
                    showLight 
    = false;
                    menuItemShowLight.Checked 
    = showLight;
                    redraw 
    = true;
                }
                
    // Create/delete light map
                if (keys[Key.L] && !shift && ctrl)    
                {
                    
    if(textureFileName.IndexOf("colors"== -1// only on textured maps
                    {
                        
    if(lightMapTexture != null) lightMapTexture.Dispose();
                        lightMapTexture 
    = null;
                        
    this.Cursor = Cursors.WaitCursor;
                        lightMapTexture 
    = LightMap(device, DEM, 1);
                        
    this.Cursor = Cursors.Default;
                        redraw 
    = true;
                    }
                }
                
    if (keys[Key.L]  && shift && ctrl)    
                {
                    
    if(lightMapTexture != null) lightMapTexture.Dispose();
                    lightMapTexture 
    = null;
                    redraw 
    = true;
                }
                
    // Create/delete section mesh
                if (keys[Key.S]  && !shift && !ctrl)    
                {
                    
    if(sectionMesh != null) sectionMesh.Dispose();
                    sectionMesh 
    = null;
                    showSection 
    = true;
                    sectionMesh 
    = TerrainSection(device, DEM);
                    showTransparentTerrain 
    = true;
                    menuItemShowSection.Checked 
    = showSection;
                    redraw 
    = true;
                }
                //按下S键+shift键
                
    if (keys[Key.S]  && shift && !ctrl)    
                {
                    
    if(sectionMesh != null) sectionMesh.Dispose();
                    sectionMesh 
    = null;
                    showSection 
    = false;
                    showTransparentTerrain 
    = false;
                    menuItemShowSection.Checked 
    = showSection;
                    redraw 
    = true;
                }
                
    // Toggle Fog
                if (keys[Key.F] && !shift)    
                {
                    showFog 
    = true;
                    menuItemShowFog.Checked 
    = showFog;
                    redraw 
    = true;
                }
                
    if (keys[Key.F] && shift)    
                {
                    showFog 
    = false;
                    menuItemShowFog.Checked 
    = showFog;
                    redraw 
    = true;
                }
                
    // Toggle map spin
                if (keys[Key.Space])    
                {
                    spin 
    = false;
                    menuItemShowSpin.Checked 
    = spin;
                    redraw 
    = true;
                }
                
    if (keys[Key.Return])    
                {
                    spin 
    = true;
                    menuItemShowSpin.Checked 
    = spin;
                    redraw 
    = true;
                }
                
    // Rotate map
                if (keys[Key.RightArrow] && !shift)    
                {
                    angle 
    -= 0.02f;
                    redraw 
    = true;
                }
                
    if (keys[Key.LeftArrow] && !shift)
                {
                    angle 
    += 0.02f;
                    redraw 
    = true;
                }
                
    if (keys[Key.UpArrow] && shift)
                {
                    angle2 
    += 0.02f;
                    redraw 
    = true;
                }
                
    if (keys[Key.DownArrow] && shift)
                {
                    angle2 
    -= 0.02f;
                    redraw 
    = true;
                }
                
    // Move map
                if (keys[Key.RightArrow] && shift)    
                {
                    dx 
    -= (float)(Math.Sin(angle) * moveFactor);
                    dy 
    -= (float)(Math.Cos(angle) * moveFactor);
                    redraw 
    = true;
                }
                
    if (keys[Key.LeftArrow] && shift)
                {
                    dx 
    += (float)(Math.Sin(angle) * moveFactor);
                    dy 
    += (float)(Math.Cos(angle) * moveFactor);
                    redraw 
    = true;
                }
                
    if (keys[Key.UpArrow] && !shift)
                {
                    dx 
    -= (float)(Math.Cos(angle) * moveFactor);
                    dy 
    += (float)(Math.Sin(angle) * moveFactor);
                    redraw 
    = true;
                }
                
    if (keys[Key.DownArrow] && !shift)
                {
                    dx 
    += (float)(Math.Cos(angle) * moveFactor);
                    dy 
    -= (float)(Math.Sin(angle) * moveFactor);
                    redraw 
    = true;
                }
                
    // Change Distance
                if (keys[Key.NumPadPlus] && !shift && !ctrl)    
                {
                    dist 
    -= dist * 0.02f;
                    redraw 
    = true;
                }
                
    if (keys[Key.NumPadMinus] && !shift && !ctrl)
                {
                    dist 
    += dist * 0.02f;
                    redraw 
    = true;
                }
                
    // Change FOV
                if (keys[Key.NumPadPlus] && shift && !ctrl)    
                {
                    fov 
    -= 0.01f;
                    redraw 
    = true;
                }
                
    if (keys[Key.NumPadMinus] && shift && !ctrl)
                {
                    fov 
    += 0.01f;
                    redraw 
    = true;
                }
                
    // Change alt scale factor (vert exaggeration)
                if ((keys[Key.X] || keys[Key.NumPadPlus]) && !shift && ctrl)    
                {
                    verticalFactor 
    *= 1.3333f;
                    MenuClearCheck(menuItemVerticalFactor);
                    menuItemVerticalFactor.MenuItems[
    0].Text = "x" + verticalFactor.ToString(CultureInfo.InvariantCulture);
                    menuItemVerticalFactor.MenuItems[
    0].Checked = true;
                    
    this.Cursor = Cursors.WaitCursor;
                    
    // Rebuilt terrain mesh
                    DisposeTerrainMesh();
                    BuildTerrainMesh();
                    
    if(sidesMesh != null) sidesMesh.Dispose();
                    sidesMesh 
    = TerrainSides(device, DEM);
                    
    this.Cursor = Cursors.Default;
                    redraw 
    = true;
                }
                
    if ((keys[Key.Z] || keys[Key.NumPadMinus]) && !shift && ctrl)
                {
                    verticalFactor 
    *= 0.75f;
                    MenuClearCheck(menuItemVerticalFactor);
                    menuItemVerticalFactor.MenuItems[
    0].Text = "x" + verticalFactor.ToString(CultureInfo.InvariantCulture);
                    menuItemVerticalFactor.MenuItems[
    0].Checked = true;
                    
    this.Cursor = Cursors.WaitCursor;
                    
    // Rebuilt terrain mesh
                    DisposeTerrainMesh();
                    BuildTerrainMesh();
                    
    if(sidesMesh != null) sidesMesh.Dispose();
                    sidesMesh 
    = TerrainSides(device, DEM);
                    
    this.Cursor = Cursors.Default;
                    redraw 
    = true;
                }
            }

    上面按键处理,主要是通过特定的按键实现一些功能的执行,跟菜单里相应项是对应的。这里并没有事件调用,只是个键盘按键响应处理方法而已,真正的调用是放在OnPaint()事件处理中1089行:

       // Read keyboard
       if(this.Focused) ReadKeyboard();

           再者,讲一些TerrainViewer的核心实现,即Direct3D编程方面。这个TerrainViewer虽然很小,总共只要三千多行代码,但这该说“麻雀虽小,五脏俱全”,我称TerrainViewer是Mini版的WorldWind一点不过分,它完全拥有了WW的核心实现:Direct3D编程和地理坐标转换。Direct3D编程如果想写好是有点难,但是要实现Direct3D编程还是不难的,因为所有的Direct3D编程的套路都是一样的,简直是工厂流水线式的。大的基本步骤为Device基本参数设置和Device初始化——》构建Mesh集合——》设置Texture——》绘制渲染参数设置——》渲染。真正的难点是构建Mesh集合,就是三维建模吧!最后渲染过程是在OnPaint()事件里实现的,这里一般会在最后调用   this.Invalidate();实现不断刷新界面实现不断重绘。

          OnPaint()里面的重绘渲染,是通过redraw标识来控制是否需要重绘的。

         else // No redraw
         {
          device.BeginScene();
          device.EndScene();
          device.Present();
          System.Threading.Thread.Sleep(50);
         }

         上面的代码很好地解决了不必要的重绘问题,WorldWind的里面的重绘是“牵一发而动全身”,我一直在想能否减少WW里不必要的重绘,难道不能尽可能地只重渲染必须的部分嘛?!WW提高效率问题可以考虑从此点开始。这部分Direct3D编程是重点,但是没有深入一步步分析各部分MESH的如何构建,因为几乎所有插件在实现三维渲染上步骤一致,实现上也是反复说Direct3D编程,几乎跟处理上WW里的一致,所以就不再赘述啦!

          最后,说一下DEM数据的使用,主要包括两方面:DEM文件使用和高程值获取。

          DEM文件使用:jpg和png直接使用,bil格式的先要转换成BitMap.请看下面LoadMap()方法的734行代码:

                    string filePath = GetFullPathTo(terrainFileName);
                    
    if(Path.GetExtension(terrainFileName) != ".bil"
                    {
                        DEM 
    = new Bitmap(filePath); // .jpg, .png
                        dem16 = false;
                    }
                    
    else
                    {
                        DEM 
    = BitmapFromBil(filePath); // .bil
                        dem16 = true;
                    }

         将bil格式转换成BitMap:

            // Converts a .bil elevation data file into a Bitmap object
            private Bitmap BitmapFromBil(string bilFile)
            {
                
    int width = 150// default size
                int height = 150;
                
    //Bitmap DEM;
                using( Stream s = File.OpenRead(bilFile))
                {
                    
    // Find out dem size
                    FileInfo demFile = new FileInfo(bilFile);
                    
    long length = demFile.Length;
                    
    if(length != 0
                    {
                        width 
    = height = (int)Math.Sqrt(length / 2);
                        
    if(width * height * 2 != length) throw new ApplicationException(".bil file size not double of a square (eg. 150x150x2)");
                    }
                    DEM 
    = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                    
    byte[] tfBuffer = new byte[width * height * 2];
                    
    if (s.Read(tfBuffer,0,tfBuffer.Length) < tfBuffer.Length)
                        
    throw new IOException(string.Format("End of file error while reading file '{0}'.", bilFile) );

                    
    int offset = 0;
                    
    for(int y = 0; y < height; y++)
                    {
                        
    for(int x = 0; x < width; x++
                        {
                            
    // 16 bit values
                            int low = tfBuffer[offset++];
                            
    int hi =  tfBuffer[offset];
                            
    int hi2 =  (short)(tfBuffer[offset++<< 8);
                            
    // Scale down to 0..255
                            int alt = (int)((float)(hi2 + low) * 255f / 9000f);
                            
    if (alt < 0) alt = 0;
                            
    if (alt > 255) alt = 255;
                            
    // Store altitude in red, and original 16 bit value in g and b
                            DEM.SetPixel(x, y, Color.FromArgb(0xff, alt, low, hi));
                        }
                    }
                }
                dem16 
    = true;
                
    return DEM;
            }

        高程值获取获取方法, float GetAlt(Bitmap DEM, float x, float y)和int GetAlt(Bitmap DEM, int x, int y)。这两个不同之处是,第一个方法里面调用了第二个方法,第二个是真正获取高程值的,而且是获取整数点上的高程值。

            // Get elevation from DEM at exact location (integer coord)
            public int GetAlt(Bitmap DEM, int x, int y)
            {
                
    int alt = 0;
                
    if(x >= 0 && x <= DEM.Width - 1 && y >= 0 && y <= DEM.Height - 1)
                {
                    Color p 
    = DEM.GetPixel(x, y);
                    alt 
    = (int)p.R; // Get altitude from red (8 bit)
                    if(dem16) 
                    {
                        
    // Get 16bit altitude from g/b
                        alt = (short)(p.B << 8+ (int)p.G;
                        
    // Check for negative values
                        if(alt > 32767) alt = 65536 - alt;
                    }
                }
                
    return alt;
            }

    第一个方法是通过调用第一个方法,然后通过插值计算的方法,获取任意点的插值。

    获取任意点高程的代码
            // Get averaged elevation from DEM at decimal location (float coord)
            public float GetAlt(Bitmap DEM, float x, float y)
            {
                
    float alt = 0f;
                
    if(x >= 0 && x <= DEM.Width - 1 && y >= 0 && y <= DEM.Height - 1)
                {
                    
    int xNW = (int)Math.Floor(x);                // North-West corner
                    int yNW = (int)Math.Floor(y);
                    
    float xF = (float)x - xNW;                    // x factor 0...1
                    float yF = (float)y - yNW;                    // y factor 0...1
                    //分别获取包含该点的最小矩形的四点的高程值
                    int altNW = GetAlt(DEM, xNW, yNW);            // Alt north-west
                    int altNE = GetAlt(DEM, xNW + 1, yNW);        // Alt north-east
                    int altSW = GetAlt(DEM, xNW, yNW + 1);        // Alt south-west
                    int altSE = GetAlt(DEM, xNW + 1, yNW + 1);    // Alt south-east
                    //插值获取该点的高程值
                    float altN = (float)altNW * (1f - xF) + (float)altNE * xF;    // North average
                    float altS = (float)altSW * (1f - xF) + (float)altSE * xF;    // South average
                    alt = altN * (1f - yF) +  altS * yF;    // North-South average
                }
                
    return alt;
            }

           太晚了,不再详细分析最后部分代码了。下次有时间说一下3D Cross Section插件加载部分的代码实现,也算是完整分析个插件功能。希望大家能有所收获。

  • 相关阅读:
    Delphi字符串函数大全
    Jackson 工具类使用及配置指南
    Mybatis中javaType和jdbcType对应关系
    Mybatis SqlSessionTemplate 源码解析
    GridView 使用技巧【转】
    Spring JSR250注解
    表格文字不换行
    Sql 分页
    从GridView生成DataTable
    故障诊断:解惑IIS服务器无法添加映射之谜
  • 原文地址:https://www.cnblogs.com/wuhenke/p/1650236.html
Copyright © 2011-2022 走看看