The Annotated ATI SDK BSP Tree Source Part III:Implementation

The Annotated ATI SDK BSP Tree Source

Part III:Implementation

丁欧南

Keyword:[Triangle Split][Collision Detection][Bounding Sphere]

此系列文章介绍包含在ATI SDK(March 2006)中的BSP Tree源代码,它实现了这些主要功能:BSP Tree 的离线编译,对第一人称视角的碰撞检测(实际上是Bounding Sphere Collision Detection).

这篇文章将接触BSP.cpp中所实现的算法.

 

1.初始化

void BTri::setup(){

       //因为逆时针旋向为正面,所以如下计算法线

       normal = normalize(cross(v[1] - v[0], v[2] - v[0]));

       //利用Part I 4.1节所述技术

       offset = -dot(v[0], normal);

       //由叉积右手定则,法线是垂直于边且指向三角形里

       edgeNormals[0] = cross(normal, v[0] - v[2]);

       edgeNormals[1] = cross(normal, v[1] - v[0]);

       edgeNormals[2] = cross(normal, v[2] - v[1]);

       //注意:未在点积前加负号,偏移量其实是反向的

       edgeOffsets[0] = dot(edgeNormals[0], v[0]);

       edgeOffsets[1] = dot(edgeNormals[1], v[1]);

       edgeOffsets[2] = dot(edgeNormals[2], v[2]);

}

 

2.线段与平面交点

vec3 planeHit(const vec3 &v0, const vec3 &v1, const vec3 &normal, const float offset){

       //使用Part I 4.2所述的技术.v0对应于S

       //dir对应于V

       //d/(dot(normal,dir))对应于t

       vec3 dir = v1 - v0;

       float d = dot(v0, normal) + offset;

       vec3 pos = v0 - (d / dot(normal, dir)) * dir;

       return pos;

}

 

3.三角形分割

 

void BTri::split(BTri *dest, int &nPos, int &nNeg, const vec3 &normal, const float offset, const float epsilon) const {

       float d[3];

       //分别取得顶点距分割面的带符号距离

       //使用了Part I 5的技术

       for (int i = 0; i < 3; i++){

              d[i] = dot(v[i], normal) + offset;

       }

       //first,second分别指示当前三角形的起讫点

       //并用以逆时针遍历三个边

       int first  = 2;

       int second = 0;

       //找寻第一组分列于分割面两侧的顶点

       while (!(d[second] > epsilon && d[first] <= epsilon)){

              first = second;

              second++;

       }

       // 处理正面的三角形分割

       nPos = 0;

       //first,second分别记录了被分割边的相应顶点

       //h是第一次相交的分割点

       vec3 h = planeHit(v[first], v[second], normal, offset);

       //h为源点,进行一次三角形扇形状的分割

       //直到遇到第二个分割点,表明正面分割完毕

       do {

              first = second;

              second++;

              if (second >= 3) second = 0;

              dest->v[0] = h;

              dest->v[1] = v[first];

              if (d[second] > epsilon){

                     dest->v[2] = v[second];

              } else {

                     //如果first,second分列于分割面两侧

//则用分割点作为最后一个正面三角形的顶点.

                     dest->v[2] = h = planeHit(v[first], v[second], normal, offset);

              }

              dest->data = data;

              dest->setup();

              dest++;

              nPos++;

       } while (d[second] > epsilon);

       // 重新初始化first,second,使其位于反面三角形的第一条边

       if (fabsf(d[second]) <= epsilon){

              first = second;

              second++;

              if (second >= 3) second = 0;

       }

       //处理反面的三角形分割

       nNeg = 0;

       //h在处理正面多边形是被记录,

       //是第二次与分割面相交的点

//h为源点,进行一次三角形扇形状的分割

       //直到遇到下一个分割点,表明反面分割完毕

       do {

              first = second;

              second++;

              if (second >= 3) second = 0;

              dest->v[0] = h;

              dest->v[1] = v[first];

              if (d[second] < -epsilon){

                     dest->v[2] = v[second];

              } else {

                     dest->v[2] = planeHit(v[first], v[second], normal, offset);

              }

              dest->data = data;

              dest->setup();

              dest++;

              nNeg++;

       } while (d[second] < -epsilon);

}

 

4.三角形相交测试

bool BTri::intersects(const vec3 &v0, const vec3 &v1) const {

       //v0,v1分别是起讫点,计算出的dir其实是反的.

       vec3 dir = v0 - v1;

       //参见 Part I 4.3 所述技术

       float k = (dot(normal, v0) + offset) / dot(normal, dir);

       //k<0k>1时都是v0,v1的延长线与三角相交,return false;

       if (k < 0 || k > 1) return false;

       vec3 pos = v0 - k * dir;

       //检测交点是否位于三角形里

       for (unsigned int i = 0; i < 3; i++){

              if (dot(edgeNormals[i], pos) < edgeOffsets[i]){

                     return false;

              }

       }

       return true;

}

 

5.判断一点是否正投影于三角形

bool BTri::isAbove(const vec3 &pos) const {

//使用暴力编码以提高效率

/*    for (unsigned int i = 0; i < 3; i++){

              if (dot(edgeNormals[i], pos) < edgeOffsets[i]){

                     return false;

              }

       }

       return true;

*/    //注意>=后的edgeOffsets无符号,这样移项之后,edgeOffsets的方向便被正了过来

       //回忆我们在setup里说的edgeOffsets反向问题

       //以下编码技术介绍在Part I 5

       return (edgeNormals[0].x * pos.x + edgeNormals[0].y * pos.y + edgeNormals[0].z * pos.z >= edgeOffsets[0] &&

                     edgeNormals[1].x * pos.x + edgeNormals[1].y * pos.y + edgeNormals[1].z * pos.z >= edgeOffsets[1] &&

                     edgeNormals[2].x * pos.x + edgeNormals[2].y * pos.y + edgeNormals[2].z * pos.z >= edgeOffsets[2]);

}

 

6.BSP Tree节点判断相交位置

bool BNode::intersects(const vec3 &v0, const vec3 &v1, const vec3 &dir, vec3 *point, const BTri **triangle) const {

    //分别计算v0,v1到平面的距离

       float d0 = dot(v0, tri.normal) + tri.offset;

       float d1 = dot(v1, tri.normal) + tri.offset;

       //记录找到的交点,用于赋给point

       vec3 pos;

       if (d0 > 0){

              if (d1 <= 0){

                     //如果v0,v1分列平面两侧,求交点

                     //但注意交点未必位于三角形上

                     pos = v0 - (d0 / dot(tri.normal, dir)) * dir;

              }

              //对于pos没有位于三角形上的情况

              //递归进入下一层节点找寻更确切的交点,见图

              if (front != NULL && front->intersects(v0, (d1 <= 0)? pos : v1, dir, point, triangle)) return true;

              if (d1 <= 0){

                     if (tri.isAbove(pos)){

                            //如果pos确实在当前三角形上,则成功.

                            if (point) *point = pos;

                            if (triangle) *triangle = &tri;

                            return true;

                     }

                     //如果交点被判断可能在反面,则递归进入反面查找

                     if (back != NULL && back->intersects(pos, v1, dir, point, triangle)) return true;

              }

       } else {

              if (d1 > 0){

                     pos = v0 - (d0 / dot(tri.normal, dir)) * dir;

              }

              if (back != NULL && back->intersects(v0, (d1 > 0)? pos : v1, dir, point, triangle)) return true;

              if (d1 > 0){

                     if (tri.isAbove(pos)){

                            if (point) *point = pos;

                            if (triangle) *triangle = &tri;

                            return true;

                     }

                     if (front != NULL && front->intersects(pos, v1, dir, point, triangle)) return true;

              }

       }

       return false;

}

 

7.Bounding Sphere Collision Detection

对于这个ATI SDK版本的碰撞检测,我要说,它是有Bug.它少了对于一个顶点与Sphere相交时的判断,并且,对于夹角小于pi/2的两个平面将导致碰撞检测失效.对于这个论题,如果你想知道更多,请在Google Groups上的comp.graphics.algorithm搜索Bounding Sphere in ATI SDK查看我与David H.Eberly的讨论.

bool BNode::pushSphere(vec3 &pos, const float radius) const {

       //取得pos到当前节点的距离

       float d = dot(pos, tri.normal) + tri.offset;

       bool pushed = false;

       if (fabsf(d) < radius){

              if (tri.isAbove(pos)){

                     //如果pos的正投影在三角形上,并且与三角形距离小于半径

                     //则将pos沿面法线方向向外推

                     // Right above the triangles

                     pos += (radius - d) * tri.normal;

                     pushed = true;

              } else {

                     //如果pos并非正投影在三角形上,但距离却小于半径时

                     //Sphere有可能与三角形的边侧交

                     // Near any of the edges?

                     vec3 v1 = tri.v[2];

                     for (int i = 0; i < 3; i++){

                            vec3 v0 = v1;

                            v1 = tri.v[i];

                            vec3 diff = v1 - v0;

                            float t = dot(diff, pos - v0);

                            if (t > 0){

                                   float f = dot(diff, diff);         

                                   if (t < f){

                                          //t<f,说明t/f<1,v的延长线未超过diff本身,

//Sphere与一条边侧交肯定

                                          vec3 v = v0 + (t / f) * diff;

                                          vec3 dir = pos - v;

                                          //沿pos在边上的垂直投影线反向推开Sphere

                                          if (dot(dir, dir) < radius * radius){

                                                 pos = v + radius * normalize(dir);

                                                 break;

                                          }

                                   }

                            }

                     }

              }

       }

       //递归进入其它边检测,以防止以上的操作将Sphere推入其它的三角形体内

       //其实防不住的,见本节首叙述

       if (front != NULL && d > -radius) pushed |= front->pushSphere(pos, radius);

       if (back  != NULL && d <  radius) pushed |= back ->pushSphere(pos, radius);

       return pushed;

}

 

8.Build the BSP Tree

void BNode::build(Array <BTri> &tris, const int cutWeight, const int unbalanceWeight){

       float epsilon = 0.001f ;

       //指引最佳分割面

       unsigned int index = 0;

       //最佳分割面的最小分值

       int minScore = 0x7FFFFFFF;

       for (unsigned int i = 0; i < tris.getCount(); i++){

              //当前分割面的分值

              int score = 0;

              //左右子树的层数差

              int diff = 0;

              for (unsigned int k = 0; k < tris.getCount(); k++){

                     //分别记录正面,反面三角形个数

                     unsigned int neg = 0, pos = 0;

                     for (unsigned int j = 0; j < 3; j++){

                            float dist = dot(tris[k].v[j], tris[i].normal) + tris[i].offset;

                            if (dist < -epsilon) neg++; else

                            if (dist >  epsilon) pos++;

                     }

                     if (pos){

                            //如果正反面顶点都存在,则必定要分解平面

                            if (neg) score += cutWeight; else diff++;

                     } else {

                            //如果正反面顶点俱无,则与当前平面共面,算作正面

                            if (neg) diff--; else diff++;

                     }

              }

              score += unbalanceWeight * abs(diff);

              if (score < minScore){

                     //取罚分最少的三角形作为分割平面

                     minScore = score;

                     index = i;

              }

       }

       //把分割平面提出,并从三角形集中删除

       tri = tris[index];

       tris.fastRemove(index);

       Array <BTri> backTris;

       Array <BTri> frontTris;

       for (unsigned int i = 0; i < tris.getCount(); i++){

              unsigned int neg = 0, pos = 0;

              for (unsigned int j = 0; j < 3; j++){

                     float dist = dot(tris[i].v[j], tri.normal) + tri.offset;

                     if (dist < -epsilon) neg++; else

                     if (dist >  epsilon) pos++;

              }

              if (neg){

                     if (pos){

                            BTri newTris[3];

                            int nPos, nNeg;

                            tris[i].split(newTris, nPos, nNeg, tri.normal, tri.offset, epsilon);

                            //dest[0,nPos)是正面三角形

                            for (int i = 0; i < nPos; i++){

                                   frontTris.add(newTris[i]);

                            }

                            //dest[nPos,nNeg)是反面三角形

                            for (int i = 0; i < nNeg; i++){

                                   backTris.add(newTris[nPos + i]);

                            }

                     } else {

                            backTris.add(tris[i]);

                     }

              } else {

                     frontTris.add(tris[i]);

              }

       }

       清空三角形集

       tris.reset();

       //递归进入下一节点进行构造,终结条件是正/反三角形集为空

       if (backTris.getCount() > 0){

              back = new BNode;

              back->build(backTris, cutWeight, unbalanceWeight);

       } else back = NULL;

       if (frontTris.getCount() > 0){

              front = new BNode;

              front->build(frontTris, cutWeight, unbalanceWeight);

       } else front = NULL;

}

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值