二叉空间分割(BSP)

二叉空间分割(BSP)
1.什么是BSP树
BSP树就是用来对N维空间中的元素进行排序和查找的二叉树。BSP树表示整个空间,BSP树中的任意一个接点表示一个凸的子空间。每个接点包含一个“超平面”,将这个接点表示的空间分割成两个子空间。每个接点除了保存其两个子接点的引用以外,还可以保存一个或多个元素。

 

对于N维空间,超平面为N-1维的对象。通常,用BSP树来表示二维或者三维空间,这时,空间中的元素分别指的是线段和多边型。

因其强大的排序和分类功能,BSP树有着非常广泛的应用。从隐藏面剔除、光线追踪到实体建模和机器人动作规划,都能看到BSP树的身影。

2.举例
认识BSP树,可以从二维空间的简单例子开始。为了简化问题,我们假定空间中的线段都是与X或者Y轴平行的,并且每次我们把空间分割成相等的两部分。比如,一个处于XY平面的正方形,第一次分割,在X方向将其分割成相等的两部分,以后的分割,按X->Y->X…的顺序进行。除非进行人为的干预,此过程将递归地进行下去。下图描述了次过程和与之对应的BSP树。

如何建立BSP树
介绍
给定三维空间中的一组多边形,我们要建立的BSP树应该包含它们当中的每一个。建立BSP树的算法是非常简单的:
1.选择分割平面
如何建立BSP树
介绍
给定三维空间中的一组多边形,我们要建立的BSP树应该包含它们当中的每一个。建立BSP树的算法是非常简单的:
1. 选择分割平面
分割平面的选择依赖于BSP树将如何被使用,以及空间中多边形的排序条件。某些情况下,使用空间中某个多边形所在的平面进行分割(叫做自动分割)。在另外一些情况下,与坐标轴垂直的平面常被用做分割平面。
在任何一种情况下,对选择分割平面的策略进行评估都是很重要的。一个很普遍的想法是让BSP树的结构尽量趋近于平衡二叉树,但是这样做是有代价的。要实现一个完全平衡的BSP树,当一个多边形与分割平面相交时,它必须被分割成两部分。一个糟糕的分割平面选择策略会产生很多这样的分割。
2. 分割多边形集合
需要根据步骤1中选择的分割平面将多边形集合分解成两个子集。如果一个多边形完全处于分割平面的一侧,只需要把它添加对应的子集中。如果与分割平面相交,先将其分成两部分,再分别添加到两个子集中。
3. 何时停止分割
判断建立BSP树的递归过程在什么条件下停止,也是与具体的应用有关的。可以在接点包含的多边形数目小于某个界限时停止,也可以当BSP树的深度超过某个界限时停止。

 

伪代码
struct BSP_tree
{
plane     partition;
list      polygons;
BSP_tree  *front, *back;
};

这个结构体定义在下面的讨论中将被一直使用。它表示BSP树的一个接点,包含有一个分割平面,处于分割平面的多边形列表,以及指向子接点的指针。

void Buld_BSP_Tree( BSP_tree *tree, list polygons )
{
    polygon *root = polygons.Get_From_List();
    tree->partition = root->Get_Plane();
    tree->polygons.Add_To_List( root );
    list front_list, back_list;
    polygon  *poly;

    while( ( poly = polygons.Get_From_List() ) != 0 )
    {
int result = tree->partition.Classify_polygon(poly);

switch( result )
{
case COINCIDENT: // 共面
     tree->polygons.Add_To_List(poly);
     break;
case IN_BACK_OF:
     back_list.Add_To_List(poly);
     break;
case IN_FRONT_OF
     front_list.Add_To_List(poly);
     break;
case SPANNING:
     polygon *front_piece, *back_piece;
   split_Polygon( poly, tree->partition, front_piece, back_piece );
     back_list.Add_To_List( back_piece );
     front_list.Add_To_List( front_piece );
     break;
 }
       }

       if( !front_list.Is->Empty_List() )
       {
tree->front = new BSP_tree;
Build_BSP_Tree( tree->front, front_list );
       }
       if( !back_list->Is_Empty_List() )
       {
tree->back = new BSP_tree;
Build_BSP_Tree( tree->back, back_list );
       }
}

Buld_BSP_Tree函数根据以上说明的步骤递归地建立BSP树。它使用输入的多边形列表中的第一个多边形所在的平面作为分割平面,假定此列表中的每一个多边形都为凸多边形。
很明显,通过更合理的分割平面选择策略,这个函数可以得到改进,以后将详细为大家阐述
如何用平面对多边形分类

 

用平面对多边形分类也就是判断平面位于多边形的哪一边。这通常被称为前/后测试,是通过测试多边形的每一个顶点与平面的位置关系来完成的。基本的算法就是遍利多边形的每一条边,找出那些两个顶点分别位于多边形两侧的边,交点将成为分割出的两个多边形的新曾顶点。

关于实现:
用平面对顶点分类,只需要把顶点的XYZ坐标值代入平面方程,AX+BY+CZ +D = 0,结果的绝对值为顶点到平面的距离,结果的符号与平面法线的方向有关。如果顶点位于平面法线所指的半平面,结果为正,否则为负。

下面是分割一个凸多边形的C++伪代码。
#define EPSILON numeric_limits<float>::epsilon( )

void SplitPolygon(polygon *poly, plane *part, polygon **front, polygon **back)
{
size_t verNum = poly->NumVertices();

point ptA, ptB;
float distA, distB;
std::vector<point> verFront, verBack;

ptA = poly->Vertex[verNum-1];
distA = poly->ClassifyPoint( ptA ); //求带符号距离

for( size_t i = EPSILON; ++i < verNum; )
{
ptB = poly->Vertex[i];
distB = poly->ClassifyPoint( ptB );

if( distB > EPSILON && distA < -EPSILON )
{
vector v = ptA - ptB;
normalize(v);
float sect = -part->ClassifyPoint(ptA) / Dot(poly->normal(), v );
verFront.push_back(ptA+(v*sect));
verBack.push_back(ptA+(v*sect));
verFront.push_back(ptB);
}
else if( distB<-EPSILON && distA>EPSILON )
{
vector v = ptB - ptA;
normalize(v);
float sect = -part->ClassifyPoint(ptB) / Dot(poly->normal(), v );
verFront.push_back(ptB+(v*sect));
verBack.push_back(ptB+(v*sect));
verBack.push_back(ptB);
}
else if( distB>EPSILON && distA>EPSILON )
{
verFront.push_back(ptB);
}
else if( distB<-EPSILON && distA<-EPSILON )
{
verBack.push_back(ptB);
}
else if( distB>-EPSILON && distB<EPSILON )
{
verFront.push_back(ptB);
verBack.push_back(ptB);
}

ptA = ptB;
distA = distB;
}

if( !verFront.empty() )
*front = new polygon(verFront);
if( !verBack.empty() )
*back = new polygon(verBack);
}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值