概述
在一些设计场景中,用户需要贴着地面生成某些构件,或者将已有的构件移到地形表面。这时,就需要一个标高查找的功能,即用户输入一个点,就返回该点在地面的铅锤投影点。
原理
Revit中的地形由一个个三角形组成的三角网构成。要获得输入点的铅锤投影点,先要查找与投影线相交的三角形,再通过线面相交获取交点。另外,当项目存在多个地面时,对每个地面去获取三角形再对每个三角形逐个判断是否相交效率很低,单个点可能几秒钟甚至几十秒才能找出交点。为了提高查找效率,可以对主体地形和它的子面域一起查找,同时只对与输入点平面距离最近的三个点相关的三角形查找交点,同时还可以对循环次数比较多的操作采取并行的方式。这样,每次查找消耗时间可以控制在0.5秒内。当找到交点时,返回交点坐标;未找到交点时,返回null。
代码实现
完整的查找方法如下:
public static XYZ GetIntersectPointOnTopographySurface(TopographySurface typography, XYZ inputPoint)
{
var document = typography.Document;
var targetTypography = !typography.IsSiteSubRegion ? typography : document.GetElement(typography.AsSiteSubRegion().HostId) as TopographySurface;
var typographyList = new List<TopographySurface>();
var meshs = new List<Mesh>();
var meshTriangles = new List<MeshTriangle>();
var points = new List<XYZ>();
typographyList.Add(targetTypography);
var subRegionIds = targetTypography.GetHostedSubRegionIds();
if (subRegionIds != null) typographyList.AddRange(subRegionIds.Select(x => document.GetElement(x) as TopographySurface));
typographyList.ForEach(x => meshs.AddRange(GetMeshs(x.get_Geometry(new Options { DetailLevel = ViewDetailLevel.Fine }))));
meshs.ForEach(x => { points.AddRange(x.Vertices); });
points.Sort((x, y) => (x - XYZ.BasisZ * y.Z).DistanceTo(inputPoint) <= (y - XYZ.BasisZ * y.Z).DistanceTo(inputPoint) ? -1 : 1);
var firstThreePoints = points.GetRange(0, 3);
Parallel.ForEach(meshs, (mesh) =>
{
for (var i = 0; i < mesh.NumTriangles; i++)
{
var meshTriangle = mesh.get_Triangle(i);
for (var j = 0; j < 3; j++)
{
if (firstThreePoints.Any(y => meshTriangle.get_Vertex(j).IsAlmostEqualTo(y)))
{
meshTriangles.Add(meshTriangle);
break;
}
}
}
});
foreach (var meshTriangle in meshTriangles)
{
try
{
var profile = new CurveLoop();
for (var j = 0; j < 3; j++)
{
profile.Append(Line.CreateBound(meshTriangle.get_Vertex(j), meshTriangle.get_Vertex((j + 1) % 3)));
}
var intersectPoint = GetIntersectPointOnCurveloop(new List<CurveLoop> { profile }, inputPoint);
if (intersectPoint == null) continue;
return intersectPoint;
}
catch { }
}
return null;
}