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()中注册了一些事件。

ExpandedBlockStart.gif 加载代码
     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,主要是设置画线的类型:单线、多条线。

ExpandedBlockStart.gif 设置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格式。

ExpandedBlockStart.gif 保存代码
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文件代码;

ExpandedBlockStart.gif 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格式文件代码

ExpandedBlockStart.gif 保存为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)方法。示例代码如下:

ExpandedBlockStart.gif 添加右键菜单代码
         ///   <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) ,来控制长度的显示。

ExpandedBlockStart.gif 添加层次控制
             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类。

ExpandedBlockStart.gif 代码
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行

ExpandedBlockStart.gif 更新光标代码
         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;
        }

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

 

 关键代码为:

ExpandedBlockStart.gif 代码
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);
        }

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值