TerrainViewer的入口Main()函数:
View Code
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 menu10 viewer.InitializeTexturesMenu();// Create Textures menu11 viewer.InitializeSidesMenu(); // Create Sides menu12 viewer.InitializeSkyMenu(); // Create Sky menu13 viewer.MapMenuSelectMap(0); // Load first map14 viewer.Show();15 Application.Run(viewer); // Run16 }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 setup2 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 map13 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 map21 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 light29 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 / angle22 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 UI101 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 try119 {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 finally145 {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 ///2 /// Background worker thread loop (updates UI) 3 /// 4 private void WorkerThreadFunc() 5 { 6 const int refreshIntervalMs = 150; // Max 6 updates per seconds 7 while(m_WorkerThreadRunning) 8 { 9 try10 {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方法:
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 else33 drawArgs.WorldCamera.TerrainElevation = 0;34 }35 else36 {37 drawArgs.WorldCamera.TerrainElevation = 0;38 }39 40 if (World.Settings.EnableAtmosphericScattering && m_outerSphere != null)41 m_outerSphere.Update(drawArgs);42 }