我在写自己的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()中注册了一些事件。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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的当前状态的(空闲、测量中、完成)。
![](https://i-blog.csdnimg.cn/blog_migrate/5bcf0c480b71be509c6c32bdfffb7885.jpeg)
从上图中,我们可看到MeasurePropertiesDialog和 SaveMultiLine类。
MeasurePropertiesDialog继承自Form,主要是设置画线的类型:单线、多条线。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
if (lineModeButton.Checked == true )
World.Settings.MeasureMode = MeasureMode.Single;
else
World.Settings.MeasureMode = MeasureMode.Multi;
this .Close();
}
SaveMultiLine类基础自Form。主要实现将画出的多线,保存为KML或Shp格式。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
// 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文件代码;
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
// 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格式文件代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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)方法。示例代码如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/// 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的集合。
{
//添加线
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(),还有一些没用到的方法(这里暂不分析)。
{
Angle angularDistance = World.ApproxAngularDistance( startLatitude, startLongitude, endLatitude, endLongitude );
Linear = angularDistance.Radians * world.EquatorialRadius;
//每两度一个点(下面计算不是好理解,但是我们可以借鉴的重点)
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()方法:
{
// 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 ( ! 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 );
}
上面代码画出的线和长度,在任何缩放级别下都是可见的,不是太好。下面是我借鉴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) ,来控制长度的显示。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
// 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类。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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
//使用自己的鼠标类型(可以借鉴学习)
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行
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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;
}
在重点处绘制矩形,原来是绘制圆圈的,其实可以在球上任意点绘制多边形的。
关键代码为:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
{
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是不会出现结果的!
drawArgs.device.DrawUserPrimitives(PrimitiveType.LineStrip, circle.Length - 1 , circle);
// drawArgs.device.DrawUserPrimitives(PrimitiveType.TriangleList, 1, circle);
drawArgs.device.Transform.World = drawArgs.WorldCamera.WorldMatrix;
drawArgs.device.VertexFormat = CustomVertex.PositionColored.Format;
}
我们最后来看一下CalculateRectPlacement()方法
{
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 ;
}
选择可见点(该代码可被我们重用:判断球面上一点是否落在可见区域中)
{
Vector3 v = new Vector3( measureLine[linePoint].X, measureLine[linePoint].Y, measureLine[linePoint].Z );
return m_drawArgs.WorldCamera.ViewFrustum.ContainsPoint(v);
}