[WorldWind学习]11.TerrainViewer插件和双线程
1.[WorldWind学习]1.接触WorldWind项目2.[WorldWind学习]2.WorldWindow控件3.[WorldWind学习]3.Device对象4.[WorldWind学习]4.DrawArgs对象5.[WorldWind学习]5.相机对象6.[WorldWind学习]6.World类7.[WorldWind学习]7.RenderableObject对象8.[WorldWind学习]8.Cache对象9.[WorldWind学习]9.WW的屏幕坐标到经纬度坐标计算10.[WorldWind学习]10.插件结构
11.[WorldWind学习]11.TerrainViewer插件和双线程
12.[WorldWind学习]12.WavingFlags和WavingFlagLayer13.[WorldWind学习]13.日志类Log14.[WorldWind学习]14.ConfigurationLoader类15.[WorldWind学习]15.模型加载16.[WorldWind学习]16.Lod技术(1)17.[WorldWind学习]17.视域调度(视域体裁剪)18.[转载]WorldWind实时确定、更新、初始化和渲染地形和纹理数据19.[WorldWind学习]18.High-Performance Timer in C#20.[WorldWind学习]19.WebDownload21.[WorldWind学习]20.修改ShapeFileLayer类及托管D3D文字绘制方法22.[World Wind学习]21.影像切割23.[World Wind学习]22.相机高度和瓦片等级计算24.[WorldWind学习]23.TerrainAccessorTerrainViewer的入口Main()函数:

1 static void Main(string[] args) 2 { 3 using (Viewer viewer = new Viewer()) 4 { 5 viewer.ProcessArgs(args); // Read command line args 6 viewer.InitializeDevice(); // Direct 3D device setup 7 viewer.InitializeKeyboard(); // Keyboard setup 8 viewer.InitializeMapList(); // Create Map menu 9 viewer.InitializeVerticalFactorMenu(); // Create vertical factor menu 10 viewer.InitializeTexturesMenu();// Create Textures menu 11 viewer.InitializeSidesMenu(); // Create Sides menu 12 viewer.InitializeSkyMenu(); // Create Sky menu 13 viewer.MapMenuSelectMap(0); // Load first map 14 viewer.Show(); 15 Application.Run(viewer); // Run 16 } 17 }
初始化设备:InitializeDevice()
鼠标交互的基本原理:
根据地图位置(参数dist)设置相机,之后相机位置保持不变。滚轮操作和键盘操作更改dist,修改相机位置。
如下为处理键盘操作的部分代码。
1 // Change Distance 2 if (keys[Key.NumPadPlus] && !shift && !ctrl) 3 { 4 dist -= dist * 0.02f; 5 redraw = true; 6 } 7 if (keys[Key.NumPadMinus] && !shift && !ctrl) 8 { 9 dist += dist * 0.02f; 10 redraw = true; 11 }
查看重载的OnPaint函数,在OnPaint中调用了CameraViewSetup()方法。
1 // Camera setup 2 private void CameraViewSetup() 3 { 4 float aspectRatio = (float)device.Viewport.Width / device.Viewport.Height; 5 device.Transform.Projection = Matrix.PerspectiveFovLH(fov, aspectRatio, mapWidth == 0 ? 15f : (float)(mapWidth / 10), mapWidth == 0 ? 5000f : (float)(mapWidth * 3)); 6 device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,dist), new Vector3(0,0,0), new Vector3(1,0,0)); 7 }
鼠标左键点击拖动,移动地图,更改了参数dx和dy。
鼠标右键点击拖动,旋转地图,更改了参数angle和angle2
1 protected override void OnMouseMove(MouseEventArgs e) 2 { 3 if(mouseDownStartPosition == Point.Empty) 4 return; 5 // Mouse drag 6 bool isMouseLeftButtonDown = ((int)e.Button & (int)MouseButtons.Left) != 0; 7 bool isMouseRightButtonDown = ((int)e.Button & (int)MouseButtons.Right) != 0; 8 double dxMouse = this.mouseDownStartPosition.X - e.X; 9 double dyMouse = this.mouseDownStartPosition.Y - e.Y; 10 if(isMouseLeftButtonDown && !isMouseRightButtonDown) 11 { 12 // Move map 13 double moveFactor = dist * 0.001f; 14 dx = mouseDownStartDx - (float)(Math.Sin(angle) * dxMouse * moveFactor) + (float)(Math.Cos(angle) * dyMouse * moveFactor); 15 dy = mouseDownStartDy - (float)(Math.Cos(angle) * dxMouse * moveFactor) - (float)(Math.Sin(angle) * dyMouse * moveFactor); 16 redraw = true; 17 } 18 if(isMouseRightButtonDown && !isMouseLeftButtonDown) 19 { 20 // Rotate map 21 double spinFactor = 0.003f; 22 angle = mouseDownStartAngle + (float)(dxMouse * spinFactor * rightClickFactor); 23 angle2 = mouseDownStartAngle2 + (float)(dyMouse * spinFactor * rightClickFactor); 24 redraw = true; 25 } 26 if(isMouseRightButtonDown && isMouseLeftButtonDown) 27 { 28 // Rotate light 29 double spinFactor = 0.003f; 30 lightHeading = mouseDownLightAngle + (float)(dxMouse * spinFactor); 31 lightElevation = mouseDownLightAngle2 + (float)(dyMouse * spinFactor); 32 redraw = true; 33 } 34 }
在OnPaint函数中调用了如下代码。
1 // Translation and Orientation angle / angle2 2 if(angle2 < 0) angle2 = 0; 3 if(angle2 > Math.PI / 2) angle2 = (float)(Math.PI / 2); 4 device.Transform.World = Matrix.Translation(dx, dy, dz); 5 device.Transform.World *= Matrix.RotationZ(angle); 6 device.Transform.World *= Matrix.RotationY(angle2);
改变世界坐标矩阵,实现平移和旋转。
这个插件和WW鼠标操作的机制很像,感觉游戏或者三维就是不断地修改参数(变量为某一限定域所有),然后重新渲染场景。
不过游戏是以第一人称观察(第三人称相机)(相机在动),物体位置不动,所以有身临其境的感觉;而这个插件的基本模式则是改变模型,是物体在动(相机不动)。还是有些区别的。
当然这个插件和ArcScene也可以改变相机,不过不是基本的。
此插件通过不断触发OnPaint事件来更改场景,WW通过Idle()来更改场景?
WW是双线程的?这个再理解一下。
MainApplication方法调用worldWindow.Render();方法,即主线程负责渲染。
WorldWindow的Render()方法启动了一个名为WorldWindow.WorkerThreadFunc线程。
1 public void Render() 2 { 3 long startTicks = 0; 4 PerformanceTimer.QueryPerformanceCounter(ref startTicks); 5 6 try 7 { 8 this.drawArgs.BeginRender(); 9 10 // Render the sky according to view - example, close to earth, render sky blue, render space as black 11 System.Drawing.Color backgroundColor = System.Drawing.Color.Black; 12 13 /*if(drawArgs.WorldCamera != null && 14 drawArgs.WorldCamera.Altitude < 1000000f && 15 m_World != null && 16 m_World.Name.IndexOf("Earth") >= 0) 17 { 18 float percent = 1 - (float)(drawArgs.WorldCamera.Altitude / 1000000); 19 if(percent > 1.0f) 20 percent = 1.0f; 21 else if(percent < 0.0f) 22 percent = 0.0f; 23 24 backgroundColor = System.Drawing.Color.FromArgb( 25 (int)(World.Settings.SkyColor.R*percent), 26 (int)(World.Settings.SkyColor.G*percent), 27 (int)(World.Settings.SkyColor.B*percent)); 28 }*/ 29 30 m_Device3d.Clear(ClearFlags.Target | ClearFlags.ZBuffer, backgroundColor, 1.0f, 0); 31 32 if (m_World == null) 33 { 34 m_Device3d.BeginScene(); 35 m_Device3d.EndScene(); 36 m_Device3d.Present(); 37 Thread.Sleep(25); 38 return; 39 } 40 41 if (m_WorkerThread == null) 42 { 43 m_WorkerThreadRunning = true; 44 m_WorkerThread = new Thread(new ThreadStart(WorkerThreadFunc)); 45 m_WorkerThread.Name = "WorldWindow.WorkerThreadFunc"; 46 m_WorkerThread.IsBackground = true; 47 if (World.Settings.UseBelowNormalPriorityUpdateThread) 48 { 49 m_WorkerThread.Priority = ThreadPriority.BelowNormal; 50 } 51 else 52 { 53 m_WorkerThread.Priority = ThreadPriority.Normal; 54 } 55 // BelowNormal makes rendering smooth, but on slower machines updates become slow or stops 56 // TODO: Implement dynamic FPS limiter (or different solution) 57 m_WorkerThread.Start();//启动后台线程 58 } 59 60 this.drawArgs.WorldCamera.Update(m_Device3d);//更新相机 61 62 m_Device3d.BeginScene(); 63 64 // Set fill mode 65 if (renderWireFrame) 66 m_Device3d.RenderState.FillMode = FillMode.WireFrame; 67 else 68 m_Device3d.RenderState.FillMode = FillMode.Solid; 69 70 drawArgs.RenderWireFrame = renderWireFrame; 71 72 // Render the current planet 73 m_World.Render(this.drawArgs);//渲染地球 74 75 if (World.Settings.ShowCrosshairs) 76 this.DrawCrossHairs(); 77 78 frameCounter++; 79 if (frameCounter == 30) 80 { 81 fps = frameCounter / (float)(DrawArgs.CurrentFrameStartTicks - lastFpsUpdateTime) * PerformanceTimer.TicksPerSecond; 82 frameCounter = 0; 83 lastFpsUpdateTime = DrawArgs.CurrentFrameStartTicks; 84 } 85 86 m_RootWidget.Render(drawArgs);//渲染部件 87 m_NewRootWidget.Render(drawArgs); 88 89 if (saveScreenShotFilePath != null) 90 SaveScreenShot(); 91 92 drawArgs.device.RenderState.ZBufferEnable = false; 93 94 // 3D rendering complete, switch to 2D for UI rendering 95 96 // Restore normal fill mode 97 if (renderWireFrame) 98 m_Device3d.RenderState.FillMode = FillMode.Solid; 99 100 // Disable fog for UI 101 m_Device3d.RenderState.FogEnable = false; 102 103 /* 104 if(World.Settings.ShowDownloadIndicator) 105 { 106 if(m_downloadIndicator == null) 107 m_downloadIndicator = new DownloadIndicator(); 108 m_downloadIndicator.Render(drawArgs); 109 } 110 */ 111 RenderPositionInfo(); 112 113 _menuBar.Render(drawArgs); 114 m_FpsGraph.Render(drawArgs); 115 116 if (m_World.OnScreenMessages != null) 117 { 118 try 119 { 120 foreach (OnScreenMessage dm in m_World.OnScreenMessages) 121 { 122 int xPos = (int)Math.Round(dm.X * this.Width); 123 int yPos = (int)Math.Round(dm.Y * this.Height); 124 Rectangle posRect = 125 new Rectangle(xPos, yPos, this.Width, this.Height); 126 this.drawArgs.defaultDrawingFont.DrawText(null, 127 dm.Message, posRect, 128 DrawTextFormat.NoClip | DrawTextFormat.WordBreak, 129 Color.White); 130 } 131 } 132 catch (Exception) 133 { 134 // Don't let a script error cancel the frame. 135 } 136 } 137 138 m_Device3d.EndScene(); 139 } 140 catch (Exception ex) 141 { 142 Log.Write(ex); 143 } 144 finally 145 { 146 147 if(World.Settings.ShowFpsGraph) 148 { 149 long endTicks = 0; 150 PerformanceTimer.QueryPerformanceCounter(ref endTicks); 151 float elapsedMilliSeconds = 1000.0f / (1000.0f*(float)(endTicks - startTicks)/PerformanceTimer.TicksPerSecond); 152 m_FrameTimes.Add(elapsedMilliSeconds); 153 } 154 this.drawArgs.EndRender(); 155 } 156 drawArgs.UpdateMouseCursor(this); 157 }
启动后台线程后调用异步方法WorkerThreadFunc()
1 /// <summary> 2 /// Background worker thread loop (updates UI) 3 /// </summary> 4 private void WorkerThreadFunc() 5 { 6 const int refreshIntervalMs = 150; // Max 6 updates per seconds 7 while(m_WorkerThreadRunning) 8 { 9 try 10 { 11 if(World.Settings.UseBelowNormalPriorityUpdateThread && m_WorkerThread.Priority == System.Threading.ThreadPriority.Normal) 12 { 13 m_WorkerThread.Priority = System.Threading.ThreadPriority.BelowNormal; 14 } 15 else if(!World.Settings.UseBelowNormalPriorityUpdateThread && m_WorkerThread.Priority == System.Threading.ThreadPriority.BelowNormal) 16 { 17 m_WorkerThread.Priority = System.Threading.ThreadPriority.Normal; 18 } 19 20 long startTicks = 0; 21 PerformanceTimer.QueryPerformanceCounter(ref startTicks); 22 23 m_World.Update(this.drawArgs);//更新世界对象 24 25 long endTicks = 0; 26 PerformanceTimer.QueryPerformanceCounter(ref endTicks); 27 float elapsedMilliSeconds = 1000*(float)(endTicks - startTicks)/PerformanceTimer.TicksPerSecond; 28 float remaining = refreshIntervalMs - elapsedMilliSeconds; 29 if(remaining > 0) 30 Thread.Sleep((int)remaining); 31 } 32 catch(Exception caught) 33 { 34 Log.Write(caught); 35 } 36 } 37 }
World的Update方法:

1 public override void Update(DrawArgs drawArgs) 2 { 3 if (!this.isInitialized) 4 { 5 this.Initialize(drawArgs); 6 } 7 8 if (this.RenderableObjects != null) 9 { 10 this.RenderableObjects.Update(drawArgs); 11 } 12 if (this.m_WorldSurfaceRenderer != null) 13 { 14 this.m_WorldSurfaceRenderer.Update(drawArgs); 15 } 16 17 if (this.m_projectedVectorRenderer != null) 18 { 19 this.m_projectedVectorRenderer.Update(drawArgs); 20 } 21 22 if (this.TerrainAccessor != null) 23 { 24 if (drawArgs.WorldCamera.Altitude < 300000) 25 { 26 if (System.DateTime.Now - this.lastElevationUpdate > TimeSpan.FromMilliseconds(500)) 27 { 28 drawArgs.WorldCamera.TerrainElevation = (short)this.TerrainAccessor.GetElevationAt(drawArgs.WorldCamera.Latitude.Degrees, drawArgs.WorldCamera.Longitude.Degrees, 100.0 / drawArgs.WorldCamera.ViewRange.Degrees); 29 this.lastElevationUpdate = System.DateTime.Now; 30 } 31 } 32 else 33 drawArgs.WorldCamera.TerrainElevation = 0; 34 } 35 else 36 { 37 drawArgs.WorldCamera.TerrainElevation = 0; 38 } 39 40 if (World.Settings.EnableAtmosphericScattering && m_outerSphere != null) 41 m_outerSphere.Update(drawArgs); 42 }
作者:太一吾鱼水
文章未经说明均属原创,学习笔记可能有大段的引用,一般会注明参考文献。
欢迎大家留言交流,转载请注明出处。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程