• WorldWind学习系列十二:Measure插件学习


      我在写自己的WorldWind插件时,遇到很大挫折,上周六本来想写个简单的画线的插件,费了九牛二虎之力终于画出了,如何以动画效果画出线的问题没解决。Direct3D中画线本来是个简单的事,画到球面上也不难,但是实践告诉我:我前期学习WW,又犯了眼高手低的毛病!改动人家写好的插件代码容易,但要把插件的整个流程都自己写,就没想象的简单啦,写代码不严谨的小问题就不说了,我周六画线的主要问题是Direct3D编程都浮在表面,连PrimitiveType中各类型的基元数和顶点的关系没搞清楚。(如想了解请参看:http://www.cnblogs.com/wuhenke/archive/2009/12/27/1633411.html红色部分)

      自己在画线上体验,让我决定先学习Measure插件。另外,我一直想做个类似VE插件,支持加载ArcGIS切图方式的影像,自己想了很久,有几个主要困惑没解决:投影方式不同如何处理、只要部分影像(如何计算行列数)、切图的中心问题(VE影像是全球的,切图中心经纬度为(0°,0°))等等。所以,前段WW实践,让我很受打击,博客就没心情更新啦!虽然理论和实践还有很大的距离,但是总结还是很重要的!

      上面都是题外话了,开始说说Measure插件吧!总体感觉Measure插件很强大,如果能搞清楚,在球面上画点、线、面都不是难事啦。(前提:要有点DirectX编程基础)

      MeasureTool.cs中有两个大类:MeasureTool(插件类)和MeasureToolLayer(渲染对象类)。MeasureToolLayer类中又包含五个内部类:MeasureLine、MeasureMultiLine 、MeasurePropertiesDialog、 MeasureState 、SaveMultiLine(如下图)

      类图

      MeasureTool作为插件类,需要实现Load() 和Unload()方法,不详说。Load()中注册了一些事件。

    加载代码
        public override void Load() 
            {
          //构造渲染对象
                layer 
    = new MeasureToolLayer(
                    
    this,
                    ParentApplication.WorldWindow.DrawArgs );
           //设置纹理路径
                layer.TexturePath 
    = Path.Combine(PluginDirectory,"Plugins\\Measure");
                ParentApplication.WorldWindow.CurrentWorld.RenderableObjects.Add(layer);

                menuItem 
    = new MenuItem("Measure\tM");
                menuItem.Click 
    += new EventHandler(menuItemClicked);
                ParentApplication.ToolsMenu.MenuItems.Add( menuItem );

                
    // Subscribe events 注册了事件
                ParentApplication.WorldWindow.MouseMove += new MouseEventHandler(layer.MouseMove);
                ParentApplication.WorldWindow.MouseDown 
    += new MouseEventHandler(layer.MouseDown);
                ParentApplication.WorldWindow.MouseUp 
    += new MouseEventHandler(layer.MouseUp);
                ParentApplication.WorldWindow.KeyUp 
    +=new KeyEventHandler(layer.KeyUp);
            }

      MeasureToolLayer作为渲染对象类,是WW插件实现的重点。必须重载的方法Initialize()、Update()、Render()和PerformSelectionAction(DrawArgs drawArgs)。

      我们先分别看看MeasureToolLayer的五个内部类。

        public enum MeasureState
        {
         Idle,
         Measuring,
         Complete
        }

      MeasureState是个枚举类型,存放Measure的当前状态的(空闲、测量中、完成)。

      从上图中,我们可看到MeasurePropertiesDialog和 SaveMultiLine类。

      MeasurePropertiesDialog继承自Form,主要是设置画线的类型:单线、多条线。

    设置MeasureMode代码
                private void okButton_Click(object sender, EventArgs e)
                {
                    
    if (lineModeButton.Checked == true
                        World.Settings.MeasureMode 
    = MeasureMode.Single;
                    
    else 
                        World.Settings.MeasureMode 
    = MeasureMode.Multi;
                    
    this.Close();
                }

      SaveMultiLine类基础自Form。主要实现将画出的多线,保存为KML或Shp格式。

    保存代码
    private void saveButton_Click(object sender, System.EventArgs e)
                {
                    
    // Heh.
                    SaveFileDialog chooser = new SaveFileDialog();
                    chooser.DefaultExt 
    = "*.csv";
                    chooser.Filter 
    = "kml files (*.kml)|*.kml|Shape files (*.shp)|*.shp";
                    chooser.Title 
    = "Save Multiline";
                    chooser.ShowDialog(MainApplication.ActiveForm);
                    String filename 
    = chooser.FileName;
                    Console.WriteLine(filename);
                    
    try
                    {
                        
    if(filename.EndsWith(".kml"))
                        {
                            StreamWriter writer = new StreamWriter(filename);
                            
    string kml = writeKML();
                            writer.WriteLine(kml);
                            writer.Close();
                        }
                        
    //need to be able to save to a network a shapefile accessible
                        if(filename.EndsWith(".shp"))
                        {
                            writeShape(filename);
                        }
                    }
                    
    catch(Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }

    输出KML文件代码;

    KML代码
        private string writeKML()
                {
                    
    //construct XML to send
                    XmlDocument doc = new XmlDocument();
                    XmlNode kmlnode 
    = doc.CreateElement("kml");
                    XmlNode node 
    = doc.CreateElement("Placemark");
                    
                    XmlNode name 
    = doc.CreateElement("name");
                    name.InnerText 
    = "New Measurement";
                    node.AppendChild(name);

                    XmlNode desc 
    = doc.CreateElement("description");
                    
    string description = "New Measurement";
                    desc.InnerXml 
    = description;
                    node.AppendChild(desc);
                
                    XmlNode polygon 
    = doc.CreateElement("Polygon");
                    
    string request = "<outerBoundaryIs><LinearRing><coordinates>";
                    
    foreach(MeasureLine line in m_multiline)
                    {
                        Double lat 
    = line.StartLatitude.Degrees;
                        Double lon 
    = line.StartLongitude.Degrees;
                        request 
    +=     lon+","+lat+",100\n";
                    }
                    request 
    += "</coordinates></LinearRing></outerBoundaryIs>";
                    
                    polygon.InnerXml
    = request;
                    node.AppendChild(polygon);

                    kmlnode.AppendChild(node);
                    doc.AppendChild(kmlnode);
                    
    return doc.OuterXml;
                }

    保存为SHP格式文件代码

    保存为Shap代码
                private void writeShape(string filename)
                {
                    IntPtr shphandle 
    = ShapeLib.SHPCreate(filename,ShapeLib.ShapeType.PolyLine);
                            
                    
    double[] lat = new double[m_multiline.Count];
                    
    double[] lon = new double[m_multiline.Count];
                            
                    
    int i=0;
                    
    foreach(MeasureLine line in m_multiline)
                    {
                        lat[i] 
    = line.StartLatitude.Degrees;
                        lon[i] 
    = line.StartLongitude.Degrees;
                        i
    ++;
                    }
                            
                    ShapeLib.SHPObject poly 
    = ShapeLib.SHPCreateSimpleObject(ShapeLib.ShapeType.Polygon,m_multiline.Count,lon,lat,null);
                    ShapeLib.SHPWriteObject(shphandle,
    0,poly);
                    ShapeLib.SHPDestroyObject(poly);
                    ShapeLib.SHPClose(shphandle);
                }

    上面是右键菜单的两个功能,如果实现添加右键菜单呢??很简单,MeasureToolLayer类只要重载RenderObject类的BuildContextMenu(ContextMenu menu)方法。示例代码如下:

    添加右键菜单代码
            /// <summary>
            
    /// Fills the context menu with menu items specific to the layer.
            
    /// </summary>
            public override void BuildContextMenu(ContextMenu menu)
            {
            menu.MenuItems.Add(
    "Properties"new System.EventHandler(OnPropertiesClick));
            menu.MenuItems.Add(
    "Save Multi-Point Line"new System.EventHandler(saveLine));
            }

    OnPropertiesClick和saveLine就是用来调用两个窗体类的。

      MeasureMultiLine继承自ArrayList,主要是存放MeasureLine的集合。

            internal class MeasureMultiLine:ArrayList
            {
                //添加线
                
    public void addLine(MeasureLine line)
                {
                    Add(line);
                }
           //删除最后一条线
                
    public void deleteLine()
                {
                    RemoveAt(Count
    -1);
                }
           //计算集合中线的总长度,我们关注如何计算单条线的长度。
                
    public double getLength() 
                {
                    
    double sum = 0.0;
                    
    foreach(MeasureLine line in this)
                        sum 
    += line.Linear;
                    
    return sum;
                }
          //线集合的渲染方法。
                
    public void Render(DrawArgs drawArgs)
                {
                    
    foreach(MeasureLine line in this)
                    {
                        
    try
                        {
                  //调用线的渲染方法
                            line.Render(drawArgs);
                        }
                        
    catch
                        {}
                    }
                }
            }

      MeasureLine继承自ListViewItem,是该Measure插件的关键部分,主要是对线对象的计算和部分渲染。这里面知识点比较重要,很多可以被我们借鉴重用。其中用到的重要方法Calculate() 和Render(),还有一些没用到的方法(这里暂不分析)。

        public void Calculate(World world, bool useTerrain)
                {
            /计算球面上两点间圆弧(对应的角度)
                    Angle angularDistance 
    = World.ApproxAngularDistance( startLatitude, startLongitude, endLatitude, endLongitude );
             //计算圆弧长度=弧度值*半径
                    Linear 
    = angularDistance.Radians * world.EquatorialRadius;
                  
    //每两度一个点(下面计算不是好理解,但是我们可以借鉴的重点)
               // 2°的弧度为 (2*PI/180)即约等于 2*3/180=1/30;(作者将PI取整为3啦)
             //每两度一个点:samples = (int)(angularDistance.Radians/2度的弧度值);
             //即 samples = (int)(angularDistance.Radians/(1/30)); 
                    
    int samples = (int)(angularDistance.Radians*30);  // 1 point for every 2 degrees.
                    if(samples<2)
                        samples 
    = 2;
             //构建点集合(线中取samples个点)
                    LinearTrackLine 
    = new CustomVertex.PositionColored[samples];
                    
    for(int i=0;i<LinearTrackLine.Length;i++)
                        LinearTrackLine[i].Color 
    = World.Settings.MeasureLineLinearColorXml;;
                
                    Angle lat,lon
    =Angle.Zero;
                    
    for(int i=0; i<samples; i++)
                    {
                        
    float t = (float)i / (samples-1);
                //计算各样本点的经纬度
                        
    World.IntermediateGCPoint(t, startLatitude, startLongitude, endLatitude, endLongitude, angularDistance, out lat, out lon );
                    
                        
    double elevation = 0;
               //计算样本点的高程(该方法可借鉴重用)
                        
    if(useTerrain)
                            elevation 
    = world.TerrainAccessor.GetElevationAt(lat.Degrees,lon.Degrees,1024);
              
     //将球面坐标,转为笛卡尔三维坐标(左手坐标系)
                        Vector3 subSegmentXyz 
    = MathEngine.SphericalToCartesian(lat, lon, 
                            world.EquatorialRadius + elevation * World.Settings.VerticalExaggeration );
                        LinearTrackLine[i].X 
    = subSegmentXyz.X;
                        LinearTrackLine[i].Y 
    = subSegmentXyz.Y;
                        LinearTrackLine[i].Z 
    = subSegmentXyz.Z;
                    }
             
    //计算两点连线的中点坐标(重点)
                    
    WorldXyzMid = world.IntermediateGCPoint(0.5f, startLatitude, startLongitude, endLatitude, endLongitude, angularDistance );
                }

       Render()方法:

                public void Render(DrawArgs drawArgs)
                {
                    
    // Draw the measure line + ends
                    Vector3 referenceCenter = new Vector3(
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.X,
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.Y,
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.Z);
             //将球体放在啥位置上!(我的理解)
                    drawArgs.device.Transform.World 
    = Matrix.Translation(
                        
    -referenceCenter
                        );

                    
    if(World.Settings.MeasureShowGroundTrack && IsGroundTrackValid)
                        drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, GroundTrackLine.Length
    -1, GroundTrackLine);
         //画出样本点的连线(注意:PrimitiveType.LineStrip类型的基元个数为LinearTrackLine.Length-1
                    drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, LinearTrackLine.Length
    -1, LinearTrackLine);

                    drawArgs.device.Transform.World 
    = drawArgs.WorldCamera.WorldMatrix;
             
    //判断一个点是否可见(方法重要)
                    if(!drawArgs.WorldCamera.ViewFrustum.ContainsPoint(WorldXyzMid))
                        
    // Label is invisible
                        return;
            //投影:将球面上的点转换为笛卡尔坐标点(重点学习)
                   
     Vector3 labelXy = drawArgs.WorldCamera.Project(WorldXyzMid - referenceCenter);
                    
    string label ="";//= Text;
                    if( groundTrack>0)
                        label 
    +=  FormatDistance(groundTrack) + Units;
                    
    else
                        label 
    +=  FormatDistance(linearDistance) + Units;  
    //在线的中点处画出线段长度(DrawText将文字渲染到球面上某点)
                   
     drawArgs.defaultDrawingFont.DrawText(null, label, (int)labelXy.X, (int)labelXy.Y, World.Settings.MeasureLineLinearColor );
                }

     上面代码画出的线和长度,在任何缩放级别下都是可见的,不是太好。下面是我借鉴VE插件代码,实现了缩放级别控制,在一定级别下才显示线的长度。

    //判断缩放级别
                public int GetZoomLevelByTrueViewRange(double trueViewRange)
                {
                    
    int maxLevel = 3//视角范围为45度
                    int minLevel = 19;
                    
    int numLevels = minLevel - maxLevel + 1;
                    
    int retLevel = maxLevel;
                    
    for (int i = 0; i < numLevels; i++)
                    {
                        retLevel 
    = i + maxLevel;

                        
    double viewAngle = 180;
                        
    for (int j = 0; j < i; j++)
                        {
                            viewAngle 
    = viewAngle / 2.0;
                        }
                        
    if (trueViewRange >= viewAngle)
                        {
                            
    break;
                        }
                    }
                    
    return retLevel;
                }

    然后,在上面的Render()里添加控制条件  if (GetZoomLevelByTrueViewRange(drawArgs.WorldCamera.TrueViewRange.Degrees) > 4) ,来控制长度的显示。

    添加层次控制
                public void Render(DrawArgs drawArgs)
                {
                    
    // Draw the measure line + ends
                    Vector3 referenceCenter = new Vector3(
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.X,
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.Y,
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.Z);

                    drawArgs.device.Transform.World 
    = Matrix.Translation(
                        
    -referenceCenter
                        );

                    
    if(World.Settings.MeasureShowGroundTrack && IsGroundTrackValid)
                        drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, GroundTrackLine.Length
    -1, GroundTrackLine);
                    drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, LinearTrackLine.Length
    -1, LinearTrackLine);

                    drawArgs.device.Transform.World 
    = drawArgs.WorldCamera.WorldMatrix;
      if (GetZoomLevelByTrueViewRange(drawArgs.WorldCamera.TrueViewRange.Degrees) > 4)
    {
                    
    if(!drawArgs.WorldCamera.ViewFrustum.ContainsPoint(WorldXyzMid))
                        
    // Label is invisible
                        return;
                    Vector3 labelXy 
    = drawArgs.WorldCamera.Project(WorldXyzMid - referenceCenter);
                    
    string label ="";//= Text;
                    if( groundTrack>0)
                        label 
    +=  FormatDistance(groundTrack) + Units;
                    
    else
                        label 
    +=  FormatDistance(linearDistance) + Units;
                    drawArgs.defaultDrawingFont.DrawText(
    null, label, (int)labelXy.X, (int)labelXy.Y, World.Settings.MeasureLineLinearColor );
    }
                }

       下面我们看一下MeasureToolLayer类。

    代码
    public override void Render(DrawArgs drawArgs)
            {
                
    if(!isOn)
                    
    return;

                
    // Turn off light
                if (World.Settings.EnableSunShading) drawArgs.device.RenderState.Lighting = false;

                
    // Check that textures are initialised
                if(!isInitialized)
                    Initialize(drawArgs);

                
    if(DrawArgs.MouseCursor == CursorType.Arrow)
             
    // Use our cursor when the mouse isn't over other elements requiring different cursor
                 //使用自己的鼠标类型(可以借鉴学习)
            DrawArgs.MouseCursor = CursorType.Measure;

                
    if(State == MeasureState.Idle)
                    
    return;
           //稍后分析
                
    if (!CalculateRectPlacement(drawArgs))
                    
    return;

                
    if(Distance < 0.01)
                    
    return;

                Device device 
    = drawArgs.device;
                device.RenderState.ZBufferEnable 
    = false;
                device.TextureState[
    0].ColorOperation = TextureOperation.Disable;
                device.VertexFormat 
    = CustomVertex.PositionColored.Format;

                
                
    // Draw the measure line + ends
                /*
                device.DrawUserPrimitives(PrimitiveType.LineStrip, measureLine.Length-1, measureLine);
                device.DrawUserPrimitives(PrimitiveType.LineStrip, startPoint.Length-1, startPoint);
                device.DrawUserPrimitives(PrimitiveType.LineList, endPoint.Length>>1, endPoint);
                
    */
           //绘制线集合
                multiline.Render(drawArgs);


                
    // Draw the info rect
            //赋予纹理
                device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;
                device.SetTexture(0,m_texture);
                device.VertexFormat 
    = CustomVertex.TransformedColoredTextured.Format;
           //绘制矩形(由两个三角形构成)
                device.DrawUserPrimitives(PrimitiveType.TriangleStrip, 
    2, rect);

                device.TextureState[
    0].ColorOperation = TextureOperation.Disable;
           //绘制连接线(三个点)
                device.DrawUserPrimitives(PrimitiveType.LineStrip, 
    2, rectLineConnection);
          //绘制矩形边框
                device.DrawUserPrimitives(PrimitiveType.LineStrip, rectFrame.Length
    -1, rectFrame);
          //渲染绘制矩形上的文字
                drawArgs.defaultDrawingFont.DrawText(
    null, labelText, labelTextRect, DrawTextFormat.None, 0xff << 24);

                device.RenderState.ZBufferEnable 
    = true;
                
    if (World.Settings.EnableSunShading) drawArgs.device.RenderState.Lighting = true;
            }

     光标问题

    DrawArgs.cs中CursorType中所有光标类型。

     /// <summary>
     /// Mouse cursor
     /// </summary>
     public enum CursorType
     {
      Arrow = 0,
      Hand,
      Cross,
      Measure,
      SizeWE,
      SizeNS,
      SizeNESW,
      SizeNWSE
     }

     更新光标方法340行

    更新光标代码
            public void UpdateMouseCursor(System.Windows.Forms.Control parent)
            {
                
    if(lastCursor == mouseCursor)
                    
    return;

                
    switch( mouseCursor )
                {
                    
    case CursorType.Hand:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.Hand;
                        
    break;
                    
    case CursorType.Cross:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.Cross;
                        
    break;
                    
    case CursorType.Measure:
                        
    if(measureCursor == null)
                       //从外界加载光标
                           
     measureCursor = ImageHelper.LoadCursor("measure.cur");
                        parent.Cursor 
    = measureCursor;
                        
    break;
                    
    case CursorType.SizeWE:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.SizeWE;
                        
    break;
                    
    case CursorType.SizeNS:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.SizeNS;
                        
    break;
                    
    case CursorType.SizeNESW:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.SizeNESW;
                        
    break;
                    
    case CursorType.SizeNWSE:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.SizeNWSE;
                        
    break;
                    
    default:
                        parent.Cursor 
    = System.Windows.Forms.Cursors.Arrow;
                        
    break;
                }
                lastCursor 
    = mouseCursor;
            }

     在重点处绘制矩形,原来是绘制圆圈的,其实可以在球上任意点绘制多边形的。

     

     关键代码为:

    代码
    public void RenderWaypointIcon(DrawArgs drawArgs, Vector3 position)
                {
                    
    if(!drawArgs.WorldCamera.ViewFrustum.ContainsPoint(position))
                        
    return;
                    
    // Draw the circle - TODO: if the circle doesn't have to always face the user it can be pre-calculated
                    Vector3 referenceCenter = new Vector3(
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.X,
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.Y,
                        (
    float)drawArgs.WorldCamera.ReferenceCenter.Z);
             //投影,将三维笛卡尔坐标系转换成平面坐标系
                    Vector3 startXy 
    = drawArgs.WorldCamera.Project(position - referenceCenter);
                    
    float circleRadius = 8;

                    for(int i=0;i<circle.Length;i++)
                    {
                        
    float angle = (float)(i*2*Math.PI/(circle.Length-1));
                //这里涉及到圆相关几何计算(看成平面圆,拿笔画画看看,不难的)
                        circle[i].X 
    = (float)(startXy.X +Math.Sin(angle)*circleRadius );
                        circle[i].Y 
    = (float)(startXy.Y +Math.Cos(angle)*circleRadius) ;
                        circle[i].Color 
    = World.Settings.MeasureLineLinearColorXml;;
                    }
                    drawArgs.device.VertexFormat 
    = CustomVertex.TransformedColored.Format;
                    drawArgs.device.Transform.World 
    = Matrix.Translation(
                        
    -referenceCenter
                        );
             //这里我有个疑惑:为啥要是TransformedColored而不是PositionColored?为什么PrimitiveType必须为线形(如:LineStrip)而不能是TrangileList??TrangileList是不会出现结果的!
                    //这里是画方形的(顶点数为5)      
             drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, circle.Length
    -1, circle);
            //这是画圆的,顶点数为8个
                  
    //  drawArgs.device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, circle);
                    drawArgs.device.Transform.World = drawArgs.WorldCamera.WorldMatrix;
                    drawArgs.device.VertexFormat 
    = CustomVertex.PositionColored.Format;
                }

     我们最后来看一下CalculateRectPlacement()方法

    bool CalculateRectPlacement(DrawArgs drawArgs)
            {
           //选择可见点(优先选中点)
                
    int labelLinePoint = FindAnchorPoint();
                
    if(labelLinePoint < 0)
                {
                    
    // Measure line is not visible
                    return false;
                }

                Vector3 referenceCenter 
    = new Vector3(
                    (
    float)drawArgs.WorldCamera.ReferenceCenter.X,
                    (
    float)drawArgs.WorldCamera.ReferenceCenter.Y,
                    (
    float)drawArgs.WorldCamera.ReferenceCenter.Z
                    );


                Angle displayAngle 
    = CalcAngle(labelLinePoint, referenceCenter);
                
    if( Angle.IsNaN(displayAngle) )
                    
    return false;

                
    const int leg1Len = 30;
                
    const int leg2Len = 5;

                
                Vector3 screenAnchor 
    = m_drawArgs.WorldCamera.Project(
                    
    new Vector3( 
                    measureLine[labelLinePoint].X,
                    measureLine[labelLinePoint].Y,
                    measureLine[labelLinePoint].Z ) 
    - referenceCenter);

                
    float x1 = (float)(screenAnchor.X + Math.Cos(displayAngle.Radians)*leg1Len);
                
    float y1 = (float)(screenAnchor.Y + Math.Sin(displayAngle.Radians)*leg1Len);
                
    float x2 = x1;
                
    float y2 = y1;


                
    // Find direction of 2nd leg.
                int quadrant = (int)((displayAngle.Radians)/(Math.PI/2));
                
    switch (quadrant % 4)
                {
                    
    case 0:
                    
    case 3:
                        x2 
    += leg2Len;
                        
    break;
                    
    case 1:
                    
    case 2:
                        x2 
    -= leg2Len;
                        
    break;
                }

                
    // Calculate label box position / size
                if (World.Settings.MeasureMode == MeasureMode.Multi)
                {
                    Distance 
    = multiline.getLength();
                    
    //labelText = Distance>=10000 ?
                    
    //    string.Format( "Total Distance: {0:f1}km", Distance/1000 ) :
                    
    //    string.Format( "Total Distance: {0:f1}m", Distance );
                    labelText = "Total Distance: " + ConvertUnits.GetDisplayString(Distance);
                }
                
    else
                {
                    
    //labelText = Distance>=10000 ?
                    
    //    string.Format( "Distance: {0:f1}km", Distance/1000 ) :
                    
    //    string.Format( "Distance: {0:f1}m", Distance );
                    labelText = "Distance: " + ConvertUnits.GetDisplayString(Distance);

                }
                labelText 
    += string.Format("\nBearing: {0:f1}", Azimuth.Degrees );
           //获取绘制文本的外矩形
                labelTextRect 
    = m_drawArgs.defaultDrawingFont.MeasureString(null, labelText, DrawTextFormat.None, 0);
                
                Rectangle tsize 
    = labelTextRect;
                
    const int xPad = 4;
                
    const int yPad = 1;
                tsize.Inflate( xPad, yPad );
                labelTextRect.Offset(
    -tsize.Left, -tsize.Top);
                tsize.Offset(
    -tsize.Left, -tsize.Top);
                
                rectLineConnection[
    0].X = screenAnchor.X;
                rectLineConnection[
    0].Y = screenAnchor.Y;
                rectLineConnection[
    1].X = x1;
                rectLineConnection[
    1].Y = y1;
                rectLineConnection[
    2].X = x2;
                rectLineConnection[
    2].Y = y2;
                
    if(x2>x1)
                {
                    labelTextRect.Offset((
    int)x2, 0);
                    tsize.Offset((
    int)x2, 0);
                }
                
    else
                {
                    
    int xof = (int)(x2-tsize.Width);
                    labelTextRect.Offset(xof, 
    0);
                    tsize.Offset(xof, 
    0);
                }
                tsize.Offset(
    0, (int)(y2 - tsize.Height/2));
                labelTextRect.Offset(
    0, (int)(y2 - tsize.Height/2));

                rect[
    0].X = tsize.Left;
                rect[
    0].Y = tsize.Top;
                rect[
    1].X = rect[0].X;
                rect[
    1].Y = tsize.Bottom;
                rect[
    2].X = tsize.Right;
                rect[
    2].Y = rect[0].Y;
                rect[
    3].X = rect[2].X;
                rect[
    3].Y = rect[1].Y;
                rect[
    4].X = rect[0].X;
                rect[
    4].Y = rect[1].Y;

                rectFrame[
    0].X = tsize.Left;
                rectFrame[
    0].Y = tsize.Top;
                rectFrame[
    1].X = rectFrame[0].X;
                rectFrame[
    1].Y = tsize.Bottom;
                rectFrame[
    2].X = tsize.Right;
                rectFrame[
    2].Y = rectFrame[1].Y;
                rectFrame[
    3].X = rectFrame[2].X;
                rectFrame[
    3].Y = rectFrame[0].Y;
                rectFrame[
    4].X = rectFrame[0].X;
                rectFrame[
    4].Y = rectFrame[0].Y;


                
    // Cap at start of measure
                Vector3 a = new Vector3(measureLine[0].X, measureLine[0].Y, measureLine[0].Z );
                Vector3 b 
    = new Vector3(measureLine[1].X, measureLine[1].Y, measureLine[1].Z );
                Vector3 vCap  
    = Vector3.Cross(a,b);
                vCap.Normalize();
                
    const int lineCapSize = 6;
                vCap.Scale( (
    float)m_drawArgs.WorldCamera.Distance/750f*lineCapSize );

                Vector3 worldXyzStart 
    = new Vector3( measureLine[0].X, measureLine[0].Y, measureLine[0].Z );
                Vector3 va 
    = Vector3.Add( worldXyzStart, vCap );
                Vector3 vb 
    = Vector3.Add( worldXyzStart, -vCap );

                startPoint[
    0].X = va.X;
                startPoint[
    0].Y = va.Y;
                startPoint[
    0].Z = va.Z;
                startPoint[
    1].X = vb.X;
                startPoint[
    1].Y = vb.Y;
                startPoint[
    1].Z = vb.Z;

                
    // Cap at end of measure
                int last = measureLine.Length-1;
                Vector3 worldXyzEnd 
    = new Vector3( 
                    measureLine[last].X,
                    measureLine[last].Y,
                    measureLine[last].Z );

                
    int beforeLast = last-1;
                vCap 
    = new Vector3( 
                    measureLine[beforeLast].X,
                    measureLine[beforeLast].Y,
                    measureLine[beforeLast].Z );
                vCap.Subtract(worldXyzEnd);
                vCap.Normalize();
                vCap.Scale( (
    float)(m_drawArgs.WorldCamera.Distance/750f*lineCapSize) );

                vb 
    = va = Vector3.Add( worldXyzEnd , vCap );
                
    const float arrowHeadAngle = 0.25f*(float)Math.PI;
                va.TransformCoordinate( Matrix.RotationAxis( worldXyzEnd, (
    float)Math.PI+arrowHeadAngle ));
                vb.TransformCoordinate( Matrix.RotationAxis( worldXyzEnd, arrowHeadAngle));

                endPoint[
    0].X = va.X;
                endPoint[
    0].Y = va.Y;
                endPoint[
    0].Z = va.Z;
                endPoint[
    1].X = vb.X;
                endPoint[
    1].Y = vb.Y;
                endPoint[
    1].Z = vb.Z;

                Matrix rotate90 
    = Matrix.RotationAxis( worldXyzEnd, (float)Math.PI*0.5f );
                va.TransformCoordinate( rotate90 );
                vb.TransformCoordinate( rotate90 );

                endPoint[
    2].X = va.X;
                endPoint[
    2].Y = va.Y;
                endPoint[
    2].Z = va.Z;
                endPoint[
    3].X = vb.X;
                endPoint[
    3].Y = vb.Y;
                endPoint[
    3].Z = vb.Z;

                
    return true;
            }

    选择可见点(该代码可被我们重用:判断球面上一点是否落在可见区域中)

            bool IsMeasureLinePointVisible(int linePoint)
            {
                Vector3 v 
    = new Vector3( measureLine[linePoint].X, measureLine[linePoint].Y, measureLine[linePoint].Z );
                
    return m_drawArgs.WorldCamera.ViewFrustum.ContainsPoint(v);
            }
  • 相关阅读:
    asp.net利用多线程执行长时间的任务,在客户端显示进度条
    电脑不能正常关机的原因和解决方法
    任务栏有两个相同的图标
    输入法不在桌面显示如何解决解决
    ASP操作XML文件的主要方法和实现
    Java开发工具下载
    感人至深的文章
    Java开发工具下载地址
    网页上图片用ps写上字之后边上锯齿严重
    队列中取最大值操作问题
  • 原文地址:https://www.cnblogs.com/wuhenke/p/1634464.html
Copyright © 2020-2023  润新知