原文链接: Toposurface Interior and Boundary Points
最近有一个问题给了我实现一个小算法的机会。我一直挺喜欢几何和逻辑分析的,之前实践的机会不多。这次算是让我过了把瘾。
提问:
我能访问到拓扑表面的点。但是我如何判断它们是内部点还是边界点呢?
一个拓扑表面的左下角的边界点:
一个拓扑表面的左下角的内部点:
我试着遍历所有的网格三角形,然后根据一个点属于几个三角形来判断它是边界点还是内部点。属于一个或两个三角形的是边界点;属于三个三角形的是内部点。但是得到的结果完全不对。这是为什么呢?
下面是我的测试代码:
/// <summary>
/// For each point of the given mesh,
/// determine how many triangles it belongs to.
/// </summary>
void DetermineTriangleCountForPoints( Mesh mesh )
{
int np = mesh.Vertices.Count;
int nt = mesh.NumTriangles;
Debug.Print( "Mesh has {0} point{1} and {2} triangle{3}.",
np, PluralSuffix( np ), nt, PluralSuffix( nt ) );
MapVertexToTriangleIndices map
= new MapVertexToTriangleIndices();
for( int i = 0; i < nt; ++i )
{
MeshTriangle t = mesh.get_Triangle( i );
for( int j = 0; j < 3; ++j )
{
map.AddVertex( t.get_Vertex( j ), i );
}
}
List<XYZ> pts = new List<XYZ>( map.Keys );
pts.Sort( Compare );
foreach( XYZ p in pts )
{
int n = map[p].Count;
Debug.Print( " vertex {0} belongs to {1} triangle{2}",
PointString( p ), n, PluralSuffix( n ) );
}
}
下面是测试代码在上图拓扑表面的运行结果(如我所说,结果是错误的)。
TopographySurface 128689:
Mesh has 6 points and 5 triangles.
(-35.952648163, -68.290878296, 0.000000000)
belongs to 3 triangles and is therefore interior
(-34.969902039, -57.134258270, 0.000000000)
belongs to 1 triangle and is therefore exterior
(-24.212404251, -76.898384094, 0.000000000)
belongs to 2 triangles and is therefore exterior
(-23.932262421, -60.340045929, 0.000000000)
belongs to 3 triangles and is therefore interior
(-23.100381851, -69.003776550, 0.000000000)
belongs to 4 triangles and is therefore exterior
(-14.513459206, -65.842926025, 0.000000000)
belongs to 2 triangles and is therefore exterior
能给我建议一个可行的区分边界点和内部点的方法吗?Revit 有这个功能,但是我不知道它是怎么做到的。顺便问一句:在 Revit 里是否有办法显示网格三角形,甚至它们的索引。
回答:
目前没有原生的 Revit API 实现这个功能。我们知道目前的拓扑表面 API 有些缺陷,不过这里有个 convex hull algorithms 算法你可以尝试一下。
记住,如果你只是想找边界,那么知道拓扑表面的 XY 位置就足够了,Z 坐标应该是可忽略的。
Revit 内部判断一个网格边界使用了如下算法:
为了定位边界点,首先必须定位边界边缘。一个边界边缘只可能属于一个三角形。所有其它的(内部)边缘都被两个三角形共用。不过判断一个边缘被几个三角形共用倒是有点儿麻烦。
我为此实现了一个算法,效果还不错。这个算法分几个步骤完成。
- 为每个网格顶点判断其所属的三角形数量。这一步和你在 DetermineTriangleCountForPoints 中实现一样。但是正如你发现的,仅依赖这个信息是不够的。
- 判断一个边缘是否是内部边缘。代码如方法 DetermineTriangleCountForEdges 所示。
这个解决方案挺可靠的。它仅需要再多一个识别点的步骤。
识别点
这个步骤有些小变化了。刚开始我使用坐标值来识别点和边缘。在第三步处理之前,我决定通过点(在网格顶点列表中)的顶点索引来识别。很明显,更可靠和高效的方式是通过一个整数值来识别一个点,而不是模糊不清的三个实数。
例如,方法 DetermineTriangleCountForEdges 就是基于一个帮助类来代表一个三角形边缘,这个帮助类包含完整的对应边缘的XYZ起始点和结束点。
public class MeshTriangleEdge
{
public XYZ A { get; set; }
public XYZ B { get; set; }
public MeshTriangleEdge( XYZ a, XYZ b )
{
int d = Compare( a, b );
Debug.Assert( 0 != d, "expected non-equal edge vertices" );
A = ( 0 < d ) ? a : b;
B = ( 0 < d ) ? b : a;
}
}
在决定了使用边缘的起始点和结束点对应的网格顶点索引来表示一条边缘之后,我转而使用下面的类来代表边缘:
/// <summary>
/// Manage a mesh triangle edge by storing the
/// index of the mesh vertex corresponing to
/// the edge start and end point.
/// For reliable comparison purposes, the
/// lower index is always stored in A and
/// the higher in B.
/// </summary>
public class JtEdge
{
public int A { get; set; }
public int B { get; set; }
public JtEdge( int a, int b )
{
Debug.Assert( a != b, "expected non-equal edge vertices" );
A = ( a < b ) ? a : b;
B = ( a < b ) ? b : a;
}
}
分类点
方法 ClassifyPoints 实现了判断一个点是内部点还是边界点的第三步。如果一个点所属的边缘都是内部边缘,则该点为内部点。如果其中有一条所属边缘是边界边缘,则该点为边界点。
我使用了多个自定义的字典帮助类来简化代码。一个是 MapEdgeToTriangles,它映射一个网格三角形边缘到一组顶点,每个顶点又包含其所属的所有三角形。
/// <summary>
/// Map mesh triangle edges to a list of the
/// indices of all the triangles they belong to.
/// </summary>
class MapEdgeToTriangles
: Dictionary<JtEdge, List<int>>
{
public MapEdgeToTriangles()
: base( new JtEdgeEqualityComparer() )
{
}
/// <summary>
/// Add a new edge.
/// If it is already known, append the triangle
/// index of the current triangle. Otherwise,
/// generate a new key for it.
/// </summary>
public void AddEdge(
JtEdge e,
int triangleIndex )
{
if( !ContainsKey( e ) )
{
Add( e, new List<int>( 2 ) );
}
( this )[e].Add( triangleIndex );
}
}
另外一个帮助类是 MapVertexToEdges,映射一个网格顶点的索引到该顶点所属的一组边缘。
/// <summary>
/// Map mesh vertex index to a list of the edges
/// the vertex belongs to.
/// </summary>
class MapVertexToEdges
: Dictionary<int, List<JtEdge>>
{
public MapVertexToEdges( int capacity )
: base( capacity )
{
}
/// <summary>
/// Append a new edge for a given vertex.
/// If the vertex is new, generate a new key for it.
/// </summary>
public void AddVertexEdge(
int vertexIndex,
JtEdge e )
{
if( !ContainsKey( vertexIndex ) )
{
Add( vertexIndex, new List<JtEdge>( 2 ) );
}
( this )[vertexIndex].Add( e );
}
}
上面两个帮助类都提供了一个增强的向字典添加元素的方法。
下面是主算法的实现:
/// <summary>
/// For each point of the given mesh,
/// determine whether it is interior or boundary.
/// The algorithm goes like this:
/// Every triangle edge belongs to either one or two triangles,
/// depending on whether it it boundary or interior.
/// For each edge, determine whether it is boundary
/// or interior. A point is interior if all of the edges it
/// belongs to are interior.
/// </summary>
/// <returns>A dictionary mapping each mesh vertex index
/// to a Boolean which is true if the corresponding point
/// is interior and false if it is on the boundary</returns>
Dictionary<int, bool> ClassifyPoints( Mesh mesh )
{
int nv = mesh.Vertices.Count;
int nt = mesh.NumTriangles;
int i, n;
Debug.Print( "\nClassifyPoints: mesh has {0} point{1} and {2} triangle{3}:",
nv, PluralSuffix( nv ), nt, PluralSuffix( nt ) );
// set up a map to determine the vertex
// index of a given triangle vertex;
// this is needed because the
// MeshTriangle.get_Vertex method
// returns the XYZ but not the index,
// and we base our edges on the index:
Dictionary<XYZ, int> vertexIndex
= new Dictionary<XYZ, int>( nv, new XyzEqualityComparer() );
for( i = 0; i < nv; ++i )
{
XYZ p = mesh.Vertices[i];
Debug.Print( " mesh vertex {0}: {1}", i, PointString( p ) );
vertexIndex[p] = i;
}
// set up a map to determine which
// edges a given vertex belongs to:
MapVertexToEdges vertexEdges
= new MapVertexToEdges( nv );
// set up a map to determine which
// triangles a given edge belongs to;
// this is used to determine the edge's
// interior or boundary status:
MapEdgeToTriangles map
= new MapEdgeToTriangles();
for( i = 0; i < nt; ++i )
{
MeshTriangle t = mesh.get_Triangle( i );
for( int j = 0; j < 3; ++j )
{
// get the start and end vertex
// of the current triangle edge:
int a = vertexIndex[t.get_Vertex( 0 == j ? 2 : j - 1 )];
int b = vertexIndex[t.get_Vertex( j )];
JtEdge e = new JtEdge( a, b );
map.AddEdge( e, i );
vertexEdges.AddVertexEdge( a, e );
vertexEdges.AddVertexEdge( b, e );
}
}
int nBoundaryEdges;
int nInteriorPoints = 0;
Dictionary<int, bool> dict = new Dictionary<int,bool>( nv );
Debug.Print( "Classify the {0} point{1}:", nv, PluralSuffix( nv ) );
for( i = 0; i < nv; ++i )
{
nBoundaryEdges = 0;
n = vertexEdges[i].Count;
nBoundaryEdges = vertexEdges[i].Count<JtEdge>(
e => 1 == map[e].Count );
dict[i] = ( 0 == nBoundaryEdges );
XYZ p = mesh.Vertices[i];
if( 0 == nBoundaryEdges )
{
++nInteriorPoints;
}
Debug.Print( " point {0} {1} belongs to {2} edge{3}, "
+ "{4} interior and {5} boundary and is therefore {6}",
i, PointString( p ), n, PluralSuffix( n ),
n - nBoundaryEdges, nBoundaryEdges,
( 0 == nBoundaryEdges ? "interior" : "boundary" ) );
}
Debug.Print( "{0} boundary and {1} interior points detected.",
nv - nInteriorPoints, nInteriorPoints );
return dict;
}
我对如下的复杂性递增的一组拓扑表面做了测试:
1. 一个三角形
2. 一个包含四个边界点的四边形
3. 另外一个多包含一个内部点的四边形
4. 一个大的包含26个点的拓扑表面
下面是测试结果:
TopographySurface 128701:
ClassifyPoints: mesh has 3 points and 1 triangle:
mesh vertex 0: (-14.39,11.53,0)
mesh vertex 1: (2.81,12.55,0)
mesh vertex 2: (-7.4,22.76,0)
triangle 0 vertex 0: (-14.39,11.53,0)
triangle 0 vertex 1: (2.81,12.55,0)
triangle 0 vertex 2: (-7.4,22.76,0)
Classify the 3 edges:
edge 0->1 belongs to 1 triangle and is therefore boundary
edge 0->2 belongs to 1 triangle and is therefore boundary
edge 1->2 belongs to 1 triangle and is therefore boundary
Classify the 3 points:
point 0 (-14.39,11.53,0) belongs to 2 edges, 0 interior and 2 boundary and is therefore boundary
point 1 (2.81,12.55,0) belongs to 2 edges, 0 interior and 2 boundary and is therefore boundary
point 2 (-7.4,22.76,0) belongs to 2 edges, 0 interior and 2 boundary and is therefore boundary
3 boundary and 0 interior points detected.
TopographySurface 128712:
ClassifyPoints: mesh has 4 points and 2 triangles:
mesh vertex 0: (25.45,10.77,0)
mesh vertex 1: (25.01,23.65,0)
mesh vertex 2: (38.32,11.22,0)
mesh vertex 3: (38.77,24.09,0)
triangle 0 vertex 0: (25.01,23.65,0)
triangle 0 vertex 1: (25.45,10.77,0)
triangle 0 vertex 2: (38.32,11.22,0)
triangle 1 vertex 0: (38.32,11.22,0)
triangle 1 vertex 1: (38.77,24.09,0)
triangle 1 vertex 2: (25.01,23.65,0)
Classify the 5 edges:
edge 0->1 belongs to 1 triangle and is therefore boundary
edge 0->2 belongs to 1 triangle and is therefore boundary
edge 1->2 belongs to 2 triangles and is therefore interior
edge 1->3 belongs to 1 triangle and is therefore boundary
edge 2->3 belongs to 1 triangle and is therefore boundary
Classify the 4 points:
point 0 (25.45,10.77,0) belongs to 2 edges, 0 interior and 2 boundary and is therefore boundary
point 1 (25.01,23.65,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 2 (38.32,11.22,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 3 (38.77,24.09,0) belongs to 2 edges, 0 interior and 2 boundary and is therefore boundary
4 boundary and 0 interior points detected.
TopographySurface 128718:
ClassifyPoints: mesh has 5 points and 4 triangles:
mesh vertex 0: (80.05,28.53,0)
mesh vertex 1: (63.18,39.63,0)
mesh vertex 2: (84.49,41.85,0)
mesh vertex 3: (75.61,52.06,0)
mesh vertex 4: (98.7,45.84,0)
triangle 0 vertex 0: (75.61,52.06,0)
triangle 0 vertex 1: (63.18,39.63,0)
triangle 0 vertex 2: (84.49,41.85,0)
triangle 1 vertex 0: (80.05,28.53,0)
triangle 1 vertex 1: (98.7,45.84,0)
triangle 1 vertex 2: (84.49,41.85,0)
triangle 2 vertex 0: (84.49,41.85,0)
triangle 2 vertex 1: (98.7,45.84,0)
triangle 2 vertex 2: (75.61,52.06,0)
triangle 3 vertex 0: (80.05,28.53,0)
triangle 3 vertex 1: (84.49,41.85,0)
triangle 3 vertex 2: (63.18,39.63,0)
Classify the 8 edges:
edge 0->1 belongs to 1 triangle and is therefore boundary
edge 0->2 belongs to 2 triangles and is therefore interior
edge 0->4 belongs to 1 triangle and is therefore boundary
edge 1->2 belongs to 2 triangles and is therefore interior
edge 1->3 belongs to 1 triangle and is therefore boundary
edge 2->3 belongs to 2 triangles and is therefore interior
edge 2->4 belongs to 2 triangles and is therefore interior
edge 3->4 belongs to 1 triangle and is therefore boundary
Classify the 5 points:
point 0 (80.05,28.53,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 1 (63.18,39.63,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 2 (84.49,41.85,0) belongs to 8 edges, 8 interior and 0 boundary and is therefore interior
point 3 (75.61,52.06,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 4 (98.7,45.84,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
4 boundary and 1 interior points detected.
TopographySurface 128725:
ClassifyPoints: mesh has 26 points and 39 triangles:
mesh vertex 0: (-87.01,-44.72,0)
mesh vertex 1: (-75.47,-26.96,0)
. . .
mesh vertex 25: (-83.46,-24.3,0)
triangle 0 vertex 0: (-87.01,-44.72,0)
triangle 0 vertex 1: (-77.69,-55.82,0)
. . .
triangle 38 vertex 2: (-75.47,-26.96,0)
Classify the 64 edges:
edge 0->8 belongs to 1 triangle and is therefore boundary
edge 0->11 belongs to 1 triangle and is therefore boundary
. . .
edge 24->25 belongs to 1 triangle and is therefore boundary
Classify the 26 points:
point 0 (-87.01,-44.72,0) belongs to 6 edges, 4 interior and 2 boundary and is therefore boundary
point 1 (-75.47,-26.96,0) belongs to 10 edges, 10 interior and 0 boundary and is therefore interior
point 2 (-55.49,-19.86,0) belongs to 10 edges, 10 interior and 0 boundary and is therefore interior
point 3 (-53.71,-33.18,0) belongs to 12 edges, 12 interior and 0 boundary and is therefore interior
point 4 (-43.06,-26.96,0) belongs to 8 edges, 6 interior and 2 boundary and is therefore boundary
point 5 (-43.06,-42.5,0) belongs to 10 edges, 10 interior and 0 boundary and is therefore interior
point 6 (-65.26,-35.4,0) belongs to 12 edges, 12 interior and 0 boundary and is therefore interior
point 7 (-60.37,-53.15,0) belongs to 12 edges, 12 interior and 0 boundary and is therefore interior
point 8 (-77.69,-55.82,0) belongs to 8 edges, 6 interior and 2 boundary and is therefore boundary
point 9 (-68.36,-43.83,0) belongs to 14 edges, 14 interior and 0 boundary and is therefore interior
point 10 (-41.28,-54.49,0) belongs to 8 edges, 6 interior and 2 boundary and is therefore boundary
point 11 (-88.34,-33.62,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 12 (-66.14,-61.14,0) belongs to 6 edges, 4 interior and 2 boundary and is therefore boundary
point 13 (-79.02,-44.28,0) belongs to 10 edges, 10 interior and 0 boundary and is therefore interior
point 14 (-81.68,-32.73,0) belongs to 14 edges, 14 interior and 0 boundary and is therefore interior
point 15 (-63.93,-26.96,0) belongs to 14 edges, 14 interior and 0 boundary and is therefore interior
point 16 (-55.05,-43.39,0) belongs to 12 edges, 12 interior and 0 boundary and is therefore interior
point 17 (-47.5,-50.93,0) belongs to 8 edges, 8 interior and 0 boundary and is therefore interior
point 18 (-74.58,-50.93,0) belongs to 8 edges, 8 interior and 0 boundary and is therefore interior
point 19 (-68.81,-54.93,0) belongs to 10 edges, 10 interior and 0 boundary and is therefore interior
point 20 (-53.71,-26.07,0) belongs to 10 edges, 10 interior and 0 boundary and is therefore interior
point 21 (-47.06,-18.97,0) belongs to 6 edges, 4 interior and 2 boundary and is therefore boundary
point 22 (-49.72,-14.98,0) belongs to 4 edges, 2 interior and 2 boundary and is therefore boundary
point 23 (-67.03,-17.64,0) belongs to 6 edges, 4 interior and 2 boundary and is therefore boundary
point 24 (-74.58,-19.42,0) belongs to 6 edges, 4 interior and 2 boundary and is therefore boundary
point 25 (-83.46,-24.3,0) belongs to 6 edges, 4 interior and 2 boundary and is therefore boundary
11 boundary and 15 interior points detected.
完整的 Revit Add-in 源代码可以在这里下载: TopoSurfacePointClassify.zip