我在写自己的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);
}