首页/文章/ 详情

接触基础知识:从二分到四叉树与八叉树

2天前浏览7

前面讲了很多具体的搜索方法,如

有限元基础知识:AABB包围盒的计算

有限元基础知识:如何计算OBB包围盒

有限元基础知识:接触检测中的分离轴定理SAT

也有说了最为基础的空间分割算法 

有限元基础知识:接触计算中的包围盒子

有限元基础知识:空间箱排序(bucket sorting)

今天就来讲一下一种经典的空间分割方法(也随之带来一种数据结构),其实这种方法很简单,我们在1维的逻辑中有一种经典方法叫做二分(遇事不决便二分),那么在2D平面上就是今天要说的四叉树,而在3D空间中就是八叉树,可以理解为     树,     为空间的维度

二分法顾名思义,就是一种逐渐缩小范围的方法,每次将搜索范围除2,比如一个人让你猜他多大岁数,你大概看出来他在25-35之间,你就先猜一下30,他说大了,你再猜一下28,......, 这样几波下来你很快就知道他多大了。

四叉树就跟这个逻辑非常一致,之前已经讲过一个接触面和另一个接触面相交,可以先确定出来一个大的相交的AABB 的 boundingbox,然后在2D中就一个这样的盒子一划四,这么划分出去,直达某个终止条件,具体可以看如下的示意图


一样的思想可以拓展到八叉树上,如下图所示 ,唯一的区别就是从平面到空间,从叉除四个节点到叉出八个节点

大家在很多论文和书上会听到有人说“树结构”画上面类似的图,初看其实可能不明觉厉,其实nothing fancy。

这个思想非常简单,那么他有啥好处呢,好处就在于可以更快速的定位,回想刚刚猜岁数的例子,猜的人也可以从25一路猜到35,而用二分的好处就是在于整个过程的时间复杂度是     , 而一个个查的复杂度是     , 这两者在数据量越大的时候效率差距会越明显。

现在想象我们在2D中用最为天真的构造方法,将接触面的主面     个单元面放到一个四叉树结构中,然后我们有     个从节点(点对面接触搜索逻辑)需要进行接触索,那么时间复杂度是啥样的呢

 

这里每一个从节点需要进行一次主面的定位,而定位主面的复杂度是    。当然还有一些优化方法可以进一步提升,但不是今天要讨论的,后续可作为番外篇。

今天就简单说了一下四叉树和八叉树,其实对于空间分割和接触检测的树状结构还有很多,也有自己不同的适用场景,比如k-d tree, BSP (二元空间分割) tree, BVH (bouding volume hierarchy) tree。总的来说今天讲的是最简单的(当然不如直接画格子的空间箱排序简单),但也有诸多细节尤其是在FEM接触检测中,下次我来详细说说,然后再展开到其他树结构。

最后我突然又想到一个好玩的事,如果各位朋友喜欢这一系列,全都看下来说不定会有意外的惊喜,比如爱打游戏的朋友,所谓的光追(Ray Tracing)底层呢也都事用的ray casting和这些空间分割树状结构啥的,然后游戏中对于接触检测不同与有限元一般称为碰撞检测(collision detection)。


来源:大狗子说数值模拟
碰撞UM游戏
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2025-06-10
最近编辑:2天前
大狗子说数值模拟
博士 传播国际一流的数值模拟算法
获赞 10粉丝 17文章 61课程 0
点赞
收藏
作者推荐

有限元基础知识:AABB包围盒的计算

上次说了接触计算中的几种不同的包围盒子,有限元基础知识:接触计算中的包围盒子下面开始分别讲一下各自的编码实现,先从最简单的AABB开始,AABB包围盒由于每个面(3D)都与坐标系平行,所以其实只需要找到,x,y,z 三个方向上所有节点的最大值和最小值就行了。下边以C++代码为例,给大家示范一下AABB的计算流程// 函数定义using point3D = std::array<double, 3>;struct AABB{ point3D min; point3D max; void init(); void addPoint(const point3D & p)};void AABB::init(){ min[0] = min[1] = min[2] = FLT_MAX; max[0] = max[1] = max[2] = -FLT_MAX;};void addPoint(const point3D & p){ if (p[0] < min[0]) min[0] = p[0]; if (p[0] > max[0]) max[0] = p[0]; if (p[1] < min[1]) min[1] = p[1]; if (p[1] > max[1]) max[1] = p[1]; if (p[2] < min[2]) min[2] = p[2]; if (p[2] > max[2]) max[2] = p[2];};// 真正的形成一个AABBAABB box;box.init();// 对于主面或者从面的节点循环// points is a vector of point3Dfor (auto p : points){ box.addPoint(p);} 那么这样一个主面或者从面的包围盒子就形成了,然而在真实的有限元计算中,由于我们需要定位潜在可能发生的接触,或者接触已经发生了,已经有了微小的穿透,以上还需要进行一点点补充,就是增加一些buffer,给现在的box扩大一圈,一般的做法如下:void AABB::addBuffer(double dis){ min[0] -= dis; min[1] -= dis; min[2] -= dis; max[0] += dis; max[1] += dis; max[2] += dis;};box.addBuffer(dis); 这样我们就完成了一个扩大化的bounding box, 如下图所示,蓝色的就是最开始计算的box,而红色虚线就是加上了buffer的box 那么然后就是如何检测两个bounding box 是否接触了,这个也非常简单,若所有轴均重叠则碰撞,否则就不bool AABBIntersect(const AABB & box1, const AABB & box2){ return (box1.min[0] <= box2.max[0] && box1.max[0] >= box2.min[0]) && (box1.min[1] <= box2.max[1] && box1.max[1] >= box2.min[1]) && (box1.min[2] <= box2.max[2] && box.max[2] >= box2.min[2]);} 这样我们就能检测两个AABB的box是否接触,做一个粗放式的检查。好了今天就先到这,明天讲一下OBB与分离轴定理SAT的编码实现,敬请期待。来源:大狗子说数值模拟

未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈