有限元基础知识:如何计算OBB包围盒
之前已经讲过AABB的编码实现,今天给大家讲一下OBB的编码实现,与AABB的编码不同,OBB会更紧贴物体,对于有限元接触仿真来说,会更贴近我们这一团网格,所以可以在前期搜索的时候更好的进行筛选。然而,OBB的计算会相对繁琐很多请看下边PCA(PrincipalComponentAnalysis)算法找到一堆节点的主轴首先找到一堆节点的的中心点其中为网格节点的坐标,具体C++代码如下usingpoint3D=std::array<double,3>;voidfindCentroid(conststd::vector<point3D>&pts,point3D&centroid){intnumber=pts.size();centroid[0]=centroid[1]=centroid[2]=0.0;for(inti=0;i<number;i++){centroid[0]+=pts[i][0];centroid[1]+=pts[i][1];centroid[2]+=pts[i][2];}centroid[0]/=n;centroid[1]/=n;centroid[2]/=n;}在得到了中心点坐标后,我们计算这一群节点与中心点的协方差矩阵,这个东西,大家顾名思义,可以理解成,这一群节点相较之这个节点有多分散,具体的算法如下这个代码也很好写,cpp代码如下voidcomputeCovariance(conststd::vector<point3D>&pts){point3Dcentroid;findCentroid(pts,centroid);std::array<point3D,3>covMat{0};for(constauto&pt:pts){doubledeltax=pt[0]-centroid[0];doubledeltay=pt[1]-centroid[1];doubledeltaz=pt[2]-centroid[2];covMat[0][0]+=deltax*deltax;covMat[1][1]+=deltay*deltay;covMat[2][2]+=deltaz*deltaz;covMat[0][1]+=deltax*deltay;covMat[0][2]+=deltax*deltaz;covMat[1][2]+=deltay*deltaz;}for(inti=0;i<3;++i){for(intj=0;j<3;++j){covMat[i][j]/=pts.size();}}covMat[1][0]=covMat[0][1];covMat[2][0]=covMat[0][2];covMat[2][1]=covMat[1][2];}然后我们通过提取协方差矩阵的特征向量,找到主轴,这个提取特征值的过程我们可以直接调用矩阵求解库中一般都有的特征值求解函数,或者由于这就是个矩阵的特征向量提取问题,我们可也可以自己写个更简单高效的[1],这里代码略,大家在很多地方都可以查到,大体上有3种写法,分列如下。voideigenMat33(constMat33&matrix,Mat33&eigenvectors,point3D&eigenvalues){//1.采用数学库比如Eigen直接进行计算//2.采用迭代求解特征值、特征向量//3.3x3矩阵特征值有解析求法,实现一下就行了}然后我们就得到了特征值与特征向量,这个特征向量3个数据方差最大的方向,也就是能找到的OBB的两两相对面的方向,现在我们终于可以构造OBB了,非常简单,其实就是将中点到各点的连线往这3个轴上进行投影,而投影嘛就是点乘,大家就记住这个操作就行了,然后简简单单的找个各个方向的最大值最小值,OBB就呼之欲出了,C++代码如下structOBB{Mat33axes;point3Dcenter;point3DhalfExtent;//半轴长};OBBcomputeOBB(conststd::vector<point3D>&pts,constMat33&eigenvectors){OBBobb;obb.axes[0]=eigenvectors[0];//最大特征值对应方向obb.axes[1]=eigenvectors[1];obb.axes[2]=eigenvectors[2];//计算各轴投影范围Vec3maxProj{-FLT_MAX,-FLT_MAX,-FLT_MAX};Vec3minProj{FLT_MAX,FLT_MAX,FLT_MAX};for(constauto&pt:pts){point3Ddir={pt[0],pt[1],pt[2]};for(inti=0;i<3;++i){doubleproj=dot(dir,obb.axes[i]);maxProj[i]=std::max(maxProj[i],proj);minProj[i]=std::min(minProj[i],proj);}}//计算中心点和半长obb.halfExtent={(maxProj[0]-minProj[0])/2.0,(maxProj[1]-minProj[1])/2.0,(maxProj[2]-minProj[2])/2.0};for(inti=0;i<3;i++){obb.center+=0.5*(maxProj[0]+maxProj[0])*obb.axes[i];}returnobb;}以上的代码就通过3个方向的投影计算了各个方向的半轴长度,与OBB的中心,注意哦,这里OBB的中心并不是刚刚计算出来的那个,一个简单的案例就是下边这个三角形:就这样我们就得到了一片Mesh的OBB,然而这种方法其实在有限元中用的不多,大家可以看到这个计算OBB的过程已经很繁琐了,然而其在游戏、CAD中用的还是不少的,但本着大家学就有用的思想,技多不压身,后面我可以单独讲一讲为啥他们在这些行业会有用。另外就是大家可能会在别的地方看到不同的OBB计算方法,但是基本思想是一致的,但是OBB的计算方法如果展开说我可以直接开一系列,最常用的一个变形就是先计算一堆节点的凸包(convexhull)再用其来计算OBB,但既然我还没讲过convexhull先就此略过,以后再补。然后还有更麻烦的呢,OBB的接触检测又需要用到一个算法,分离轴定理(SAT),其实也可以直接用GJK算法,这些也挺繁琐,有待下回分解。参考文献:[1]Siddique,AbuBakar,andTariqA.Khraishi.“EigenvaluesandEigenvectorsfor3x3SymmetricMatrices:AnAnalyticalApproach.”JournalofAdvancesinMathematicsandComputerScience35.7(2020):106-118来源:大狗子说数值模拟