Bullet 引擎 详解 DBVT 分析

1649 篇文章 11 订阅
1277 篇文章 12 订阅

DBVT 在bullet 引擎中是很基础且重要的一个数据结构,本质上是一个可以动态更新的AABB树。在bullet的远距阶段是很高效的碰撞检测数据结构(比较OOB,K- DOP)。是组成dbvtbroadphase的重要成员。

首先看看树中节点的定义

  1. struct  btDbvtNode  
  2. {  
  3.     btDbvtVolume    volume;    // point to the bounding volume   
  4.     btDbvtNode*     parent;    // point to parent node   
  5.     DBVT_INLINE bool    isleaf() const      { return(childs[1]==0); }  
  6.     DBVT_INLINE bool    isinternal() const  { return(!isleaf()); }  
  7.     union  
  8.     {  
  9.         btDbvtNode* childs[2]; // point to child nodes if node is internal type   
  10.         void*   data;          // point to Dbvtproxy object if node is leaf node     
  11.         int     dataAsInt;     // padding ?    
  12.     };  
  13. };  

很明显这是一个典型的二叉树节点,同时当node是内部节点时将指向2个子结点,node是叶子节点时,将指向用户定义数据(具体见后续分析)

 

 


 

接下来是DBVT 的部分基础定义

  1. struct  btDbvt  
  2. {  
  3.     // Fields   
  4.     btDbvtNode*     m_root;    // root node of the tree    
  5.     btDbvtNode*     m_free;    // node buffer last one deleted   
  6.     int             m_lkhd;    // number of look ahead   
  7.     int             m_leaves;  // number of nodes    
  8.     unsigned        m_opath;   // bitmap, mean the path to the node    
  9. }  

这里的look ahead 基本没有在代码中用到。 m_opath将在优化中使用,主要用于纪录通往特定节点的路径。

 


 

创建节点比较直接,唯一值得注意的就是利用到了m_free 这个最后被从树中删除的节点(节点分配内存未释放)

如果m_free仍未被释放就重复利用,节省一次malloc调用。

 

  1. static DBVT_INLINE btDbvtNode*  createnode( btDbvt* pdbvt,  
  2.                                            btDbvtNode* parent,  
  3.                                            void* data)  
  4. {  
  5.     btDbvtNode* node;  
  6.     //if the node pool is avaliable   
  7.     if(pdbvt->m_free)   
  8.     { node=pdbvt->m_free;pdbvt->m_free=0; }  // if yes, use it and reset the pointer   
  9.     else  
  10.     { node=new(btAlignedAlloc(sizeof(btDbvtNode),16)) btDbvtNode(); } // otherwise alloc memory to node   
  11.     node->parent =   parent;  // set the parent   
  12.     node->data       =   data;    // set the data   
  13.     node->childs[1]  =   0;       // set the right child pointer as  null   
  14.     return(node);  
  15. }  


 

插入节点到树中较为复杂,主要算法是插入到树中距离被插入节点距离(曼哈顿距离)最近的节点,并且合成新的父节点,并且向上传导包围体的变化(复习一下AABB)。

insert_node


 

删除节点和插入节点比较类似,主要算法是用兄弟节点替换父节点,同时向上传导产生的包围体变化。

 

remove_node


节点排序,检查父节点和字节点对象的地址,如果父节点地址高于子节点,则交换父子节点,

 

  1. //make sure the parent's address is smaller than child node    
  2. static DBVT_INLINE btDbvtNode*  sort(btDbvtNode* n,btDbvtNode*& r) // r is reference     
  3. {  
  4.     btDbvtNode* p=n->parent;  
  5.     btAssert(n->isinternal());    
  6.     if(p>n) //all idea is swap the postion betwwen p and n . if the n address is smaller than p address.   
  7.     {  
  8.         const int       i=indexof(n);  
  9.         const int       j=1-i;  
  10.         btDbvtNode* s=p->childs[j];  // get the sibling node   
  11.         btDbvtNode* q=p->parent;     // get the grand father node   
  12.         btAssert(n==p->childs[i]);   // confirm again!   
  13.         if(q) q->childs[indexof(p)]=n; else r=n;  
  14.         s->parent=n;  // reset the sibling node's parent to node   
  15.         p->parent=n;  // reset the parent's parent  to node   
  16.         n->parent=q;  // reset the node's parent to grand father   
  17.         p->childs[0]=n->childs[0]; //reset  parent node' child node to node's child   
  18.         p->childs[1]=n->childs[1]; //reset  parent node' child node to node's child   
  19.         n->childs[0]->parent=p;    //reset  node's child node's parent node to parent   
  20.         n->childs[1]->parent=p;    //reset  node's child node's parent node to parent   
  21.         n->childs[i]=p;            //reset  node's child to parent node   
  22.         n->childs[j]=s;            //reset  node's child to parent node   
  23.         btSwap(p->volume,n->volume); //swap the volume    
  24.         return(p);  //make sure return the greater one    
  25.     }  
  26.     return(n); //make sure return the greater one    
  27. }  

 

 

开始阅读前可以先浏览一下之前框架分析

 

 

在物理模拟的场景中, 一个节点首先应该具备2种属性

 

  • 物理属性 包含 质量,速度,惯性,加速度,角速度,约束,摩擦系数 等
  • 几何属性  形状, 包围体层次,碰撞检测类型掩码。world 变换。

物理属性大部分都在collosionObject 和rigidObjec 这样的类之中。
几何属性则又一次被细分为包围体层次和形状,碰撞检测类型掩码。

为了进一步介绍Dbvt的核心函数 btDbvt::collideTV
必须首先引入btDbvtProxy。btDbvtProxy 继承自 btBroadphaseProxy。

  1. ATTRIBUTE_ALIGNED16(struct) btBroadphaseProxy  
  2. {  
  3.     ///optional filtering to cull potential collisions   
  4.     enum CollisionFilterGroups  
  5.     {  
  6.             DefaultFilter = 1,  
  7.             StaticFilter = 2,  
  8.             KinematicFilter = 4,  
  9.             DebrisFilter = 8,  
  10.                 SensorTrigger = 16,  
  11.                 CharacterFilter = 32,  
  12.             AllFilter = -1 //all bits sets: DefaultFilter | StaticFilter | KinematicFilter | DebrisFilter | SensorTrigger   
  13.     };  
  14.   void*     m_clientObject;//Usually the client btCollisionObject or Rigidbody class   
  15.     short int m_collisionFilterGroup; //碰撞检测类型   
  16.     short int m_collisionFilterMask;  //碰撞检测类型掩码   
  17.     void   *    m_multiSapParentProxy;  //sweep and prune broadphase 测试研究用   
  18.     int           m_uniqueId;//m_uniqueId 在重叠节点管理结构中要用到,非必须   
  19.     btVector3   m_aabbMin; //store the volume   
  20.     btVector3   m_aabbMax; //store the volume   
  21. }  

btBroadphaseProxy 基类包含了最基本的碰撞检测类型以及对象本身支持的碰撞检测类型,关于碰撞检测类型的详细描述可以参见bullet手册的第5章节有详细描述。对象可以根据支持的碰撞检测类型选择碰撞的对象。还包含了指向物理属性对象的指针。
再看btDbvtProxy

  1. struct btDbvtProxy : btBroadphaseProxy  
  2. {  
  3.         /* Fields                */   
  4.         btDbvtNode*        leaf;       //指向Dbvt 中对应的节点   
  5.         btDbvtProxy*        links[2];  //指向双向列表的前后节点 见btDbvtBroadphase分析   
  6.         int                    stage;    //所处的队列 见btDbvtBroadphase分析   
  7. }  

btDbvtProxy 是一个节点,利用了代理的设计模式,连接物理属性节点与几何节点。
本身还是重合节点管理的重要组成部分。然后再看看btDbvtTreeCollider

  1. /* Tree collider        */   
  2. struct        btDbvtTreeCollider : btDbvt::ICollide  
  3. {  
  4.         btDbvtBroadphase*        pbp;  
  5.         btDbvtProxy*                proxy;  
  6.         void        Process(const  btDbvtNode* n)  
  7.         {  
  8.                 Process(n,proxy->leaf);  
  9.         }  
  10. }  

btDbvtTreeCollider 定义了当两个几何节点在远距状态下碰撞后的处理方法,btDbvt::ICollide 定义的是接口标准btDbvtTreeCollider 是在dbvt下的一个具体实现,开发者也可以根据自己的需要嵌入不同的处理逻辑。proxy代表的是主节点,n代表的是被比较节点。
现在进入主题 btDbvt::collideTV。这个函数的主要算法是遍历所有和目标节点重合的叶子节点,然后使用已有的处理逻辑模块 btDbvtTreeCollider  来处理这两个节点。会引入新的数据结构btHashedOverlappingPairCache,可以参见后续的btDbvtBroadphase分析

  1. btDbvt::collideTV(const btDbvtNode* root,  
  2.                   const btDbvtVolume& vol,  
  3.                                                                   DBVT_IPOLICY)  
  4. {  
  5. ATTRIBUTE_ALIGNED16(btDbvtVolume)                volume(vol);  
  6. btAlignedObjectArray<const btDbvtNode*>        stack;  
  7. stack.resize(0);  
  8. stack.reserve(SIMPLE_STACKSIZE); //生成一个栈用来遍历Dbvt   
  9. stack.push_back(root);                    //根节点入栈   
  10. do        {  
  11.         const btDbvtNode*        n=stack[stack.size()-1];  // 栈顶元素出栈   
  12.         stack.pop_back();                             // 栈顶元素出栈   
  13.         if(Intersect(n->volume,volume))// 如果这个节点包含了目标节点   
  14.         {  
  15.                 if(n->isinternal())// 同时是内部节点,则遍历其子树   
  16.                 {  
  17.                         stack.push_back(n->childs[0]);  //左孩子入栈   
  18.                         stack.push_back(n->childs[1]);  //右孩子入栈   
  19.                 }  
  20.                 else //如果已经是叶子节点   
  21.                 {  
  22.                         policy.Process(n); // 利用既有的处理策略来处理这一对碰撞的节点   
  23.                 }  
  24.         }  
  25. while(stack.size()>0); // run until all intersect leaf node is visited .   
  26. }  

 

接着上次关于Dbvt得分析,不过漏掉了一个比较重要的函数
function btDbvt:: collideTTpersistentStack,  btDbvt::collideTT
    * btDbvt:: collideTTpersistentStack. 负责进行两个dbvt子树的比较,找出两个子树中重叠的节点对,基于一个全局栈(一个成员变量实例)
    * btDbvt::collideTT.  负责进行两个dbvt子树的比较,找出两个子树中重叠的节点对,但是基于的是一个局部栈(函数调用结束则释放)。
    * btDbvt::collideTV. 负责在一个树中搜索和对应包围体重叠的节点。
btDbvt:: collideTTpersistentStack 的算法主要是遍历两个目标子树所有可能重合的节点对(基于栈的遍历)只要两个节点中有一个不是叶子节点则压入栈中直到两个节点都为叶子节点且相互重叠,都调用预先制定好的碰撞逻辑来处理。

  1. /* Stack element    Node pair*/  
  2. struct    sStkNN  
  3. {  
  4. const btDbvtNode*    a;  
  5. const btDbvtNode*    b;  
  6. }  

  1. m_stkStack.resize(DOUBLE_STACKSIZE);  
  2. m_stkStack[0]=sStkNN(root0,root1);   // push both sub tree root nodes into stack   
  3. do        {  
  4. sStkNN        p=m_stkStack[--depth]; //pop out the top of stack   
  5. if(depth>treshold)//dynamic expand stack size   
  6. {  
  7. m_stkStack.resize(m_stkStack.size()*2);  
  8. treshold=m_stkStack.size()-4;  
  9. }  
  10. if(p.a==p.b) // If compare the same sub-tree   
  11. {  
  12. if(p.a->isinternal())//and if node is internal node   
  13. {  
  14. //push 3 possible branch into stack   
  15. //1 compare the same left sub tree   
  16. m_stkStack[depth++]=sStkNN(p.a->childs[0],p.a->childs[0]);  
  17. //2 compare the same right sub tree   
  18. m_stkStack[depth++]=sStkNN(p.a->childs[1],p.a->childs[1]);  
  19. //3 compare the left subtree and right subtree   
  20. m_stkStack[depth++]=sStkNN(p.a->childs[0],p.a->childs[1]);  
  21. }  
  22. }  
  23. //if compare different sub-tree then test if the   
  24. // the volume is intersect   
  25. else if(Intersect(p.a->volume,p.b->volume))  
  26. {  
  27. if(p.a->isinternal()) // if a is internal node   
  28. {  
  29. if(p.b->isinternal())  
  30. {  
  31. //if both node are internal node   
  32. //push 4 possible branch into stack   
  33. //1 compare the a left and b left subtree   
  34. m_stkStack[depth++]=sStkNN(p.a->childs[0],p.b->childs[0]);  
  35. //2 compare the a right and b left subtree   
  36. m_stkStack[depth++]=sStkNN(p.a->childs[1],p.b->childs[0]);  
  37. //3 compare the a left and b right subtree   
  38. m_stkStack[depth++]=sStkNN(p.a->childs[0],p.b->childs[1]);  
  39. //4 compare the a right and b right subtree   
  40. m_stkStack[depth++]=sStkNN(p.a->childs[1],p.b->childs[1]);  
  41. }  
  42. else //if b is leaf node   
  43. {  
  44. //1 compare the a left and b node   
  45. m_stkStack[depth++]=sStkNN(p.a->childs[0],p.b);  
  46. //1 compare the a right and b node   
  47. m_stkStack[depth++]=sStkNN(p.a->childs[1],p.b);  
  48. }  
  49. }  
  50. else // if a is leaf node   
  51. {  
  52. if(p.b->isinternal()) // if b is internal node   
  53. {  
  54. //1 compare a and b left sub tree   
  55. m_stkStack[depth++]=sStkNN(p.a,p.b->childs[0]);  
  56. //2  compare a and b right sub tree   
  57. m_stkStack[depth++]=sStkNN(p.a,p.b->childs[1]);  
  58. }  
  59. else  
  60. {     //if both a and b is leaf node   
  61. policy.Process(p.a,p.b);  
  62. }  
  63. }  
  64. }  
  65. while(depth);  

光线与AABB 相交检测:
这是一个非常经典的问题, <<real time collision detection>> 5.33章节有非常详尽的讨论。
下面是光线的方程

  t是可变参数, P是光线的起始点 RayFrom, d是光线的方向向量

 

下面这个是平面的方程

 

 

向量n是平面的法向量,所以如果光线与任何一个平面相交,应该有如下等式

 

对于AABB来说是六个平面, 每个面的法向量为(0,0,1)(0,1,0) ..... 总之三个坐标有两个是0,另外的一个是1或者-1
AABB同时是3个平面槽的交集, 光线与AABB最多有2个相交点,一个是前景点,一个后景点,分别与两个平面相交。
特例是完全与一个平面重合。可以被认为是和相邻的两个平面相交。
btRayAabb2.是bullet中用于检测的相关函数

 

  1. SIMD_FORCE_INLINE bool btRayAabb2(const btVector3& rayFrom,  // point P   
  2. const btVector3& rayInvDirection,  // Direction Vector inverse   
  3. const unsigned int raySign[3],  
  4. const btVector3 bounds[2],    // min x, max x AABB volume   
  5. btScalar& tmin,  
  6. btScalar lambda_min,  
  7. btScalar lambda_max)  
  8. {  
  9. btScalar tmax, tymin, tymax, tzmin, tzmax;  
  10. // get the far plane intersect param t along x axis   
  11. tmax = (bounds[1-raySign[0]].getX() - rayFrom.getX()) * rayInvDirection.getX();  
  12. // get the near plane intersect param t along y axis   
  13. tymin = (bounds[raySign[1]].getY() - rayFrom.getY()) * rayInvDirection.getY();  
  14. // get the far plane intersect param t along y axis   
  15. tymax = (bounds[1-raySign[1]].getY() - rayFrom.getY()) * rayInvDirection.getY();  
  16. // if it is not intersect with any plane then exit   
  17. if ( (tmin > tymax) || (tymin > tmax) )  
  18. return false;  
  19. if (tymin > tmin)  
  20. tmin = tymin;  // update the tmin   
  21. if (tymax < tmax)  
  22. tmax = tymax; // update tmax   
  23. // get the near plane intersect param t along Z axis   
  24. tzmin = (bounds[raySign[2]].getZ() - rayFrom.getZ()) * rayInvDirection.getZ();  
  25. // get the far plane intersect param t along Z axis   
  26. tzmax = (bounds[1-raySign[2]].getZ() - rayFrom.getZ()) * rayInvDirection.getZ();  
  27. // if it is not intersect with any plane then exit   
  28. if ( (tmin > tzmax) || (tzmin > tmax) )  
  29. return false;  
  30. // caculate the interval   
  31. if (tzmin > tmin)  
  32. tmin = tzmin;   // if find nearer point update tmin   
  33. if (tzmax < tmax)  
  34. tmax = tzmax;   // if found the farer point update tmax   
  35. return ( (tmin < lambda_max) && (tmax > lambda_min) );  
  36. }  

 

 

现在可以讨论ayTestInternal 函数, 这个函数基本的算法就是遍历所有的节点(基于栈)
光线讲和树中的每个节点做相交测试,如果相交就继续处理对应的子结点,否则就跳过。
对应所有最终的相交叶子节点,调用相交处理逻辑(回调函数)来处理。

 

  1. do  
  2. {  
  3. //pop out the top of stack   
  4. const btDbvtNode* node=stack[--depth];  
  5. //set up the AABB BOX   
  6. bounds[0] = node->volume.Mins()-aabbMax;  
  7. bounds[1] = node->volume.Maxs()-aabbMin;  
  8. btScalar tmin=1.f,lambda_min=0.f;  
  9. unsigned int result1=false;  
  10. // Do the intersect tes t!!   
  11. result1 = btRayAabb2(rayFrom,rayDirectionInverse,  
  12. signs,bounds,tmin,lambda_min,  
  13. lambda_max);  
  14. if(result1)  
  15. {  
  16. //if test pass   
  17. if(node->isinternal())  
  18. //if node is intertal   
  19. if(depth>treshold) //dynamic expand stack   
  20. {  
  21. stack.resize(stack.size()*2);  
  22. treshold=stack.size()-2;  
  23. }  
  24. //push the left child into stack   
  25. stack[depth++]=node->childs[0];  
  26. //push the right child into stack   
  27. stack[depth++]=node->childs[1];  
  28. }  
  29. else  
  30. {  
  31. //if node is leaf node,then process it by callback   
  32. policy.Process(node);  
  33. }  
  34. }  
  35. while(depth);  

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值