我在写自己的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); }
转载于:https://www.cnblogs.com/wuhenke/archive/2009/12/28/1634464.html
相关资源:数据结构—成绩单生成器