首页/文章/ 详情

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

25天前浏览250

之前已经讲过AABB的编码实现,今天给大家讲一下OBB的编码实现,与AABB的编码不同,OBB会更紧贴物体,对于有限元接触仿真来说,会更贴近我们这一团网格,所以可以在前期搜索的时候更好的进行筛选。然而,OBB的计算会相对繁琐很多请看下边

PCA(Principal Component Analysis)算法找到一堆节点的主轴

首先找到一堆节点的的中心点

 

其中    为网格节点的坐标,具体C++代码如下

using point3D = std::array<double3>;

void findCentroid(const std::vector<point3D> & pts, point3D & centroid)
{
 int number = pts.size();
 centroid[0] = centroid[1] = centroid[2] = 0.0;
 for (int i = 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代码如下

void computeCovariance(const std::vector<point3D> & pts)
{
 point3D centroid;
 findCentroid(pts, centroid);
 
    std::array<point3D, 3> covMat {0};
    for (const auto& pt : pts) {
        double deltax = pt[0] - centroid[0];
        double deltay = pt[1] - centroid[1];
        double deltaz = 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 (int i = 0; i < 3; ++i) {
        for (int j = 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种写法,分列如下。

void eigenMat33(const Mat33 & matrix, Mat33& eigenvectors, point3D& eigenvalues) {
    // 1. 采用数学库比如 Eigen直接进行计算

    // 2. 采用迭代求解特征值、特征向量

    // 3. 3x3 矩阵特征值有解析求法,实现一下就行了
}

然后我们就得到了特征值与特征向量,这个特征向量3个数据方差最大的方向,也就是能找到的OBB的两两相对面的方向,现在我们终于可以构造OBB了,非常简单,其实就是将中点到各点的连线往这3个轴上进行投影,而投影嘛就是点乘,大家就记住这个操作就行了,然后简简单单的找个各个方向的最大值最小值,OBB就呼之欲出了,C++代码如下

struct OBB
{

 Mat33 axes;
 point3D center;
 point3D halfExtent; // 半轴长
};


OBB computeOBB(const std::vector<point3D>& pts, const Mat33 & eigenvectors) {
    OBB obb;
    
    obb.axes[0] = eigenvectors[0]; // 最大特征值对应方向
    obb.axes[1] = eigenvectors[1];
    obb.axes[2] = eigenvectors[2];
    
    // 计算各轴投影范围
    Vec3 maxProj{-FLT_MAX, -FLT_MAX, -FLT_MAX};
    Vec3 minProj{FLT_MAX, FLT_MAX, FLT_MAX};
    for (const auto& pt : pts) {
        point3D dir = {pt[0], pt[1], pt[2]};
        for (int i = 0; i < 3; ++i) {
            double proj = 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 (int i = 0; i< 3; i++)
    {
     obb.center += 0.5 * (maxProj[0] + maxProj[0])*obb.axes[i];
    }
    
    return obb;
}

以上的代码就通过3个方向的投影计算了各个方向的半轴长度,与OBB的中心,注意哦,这里OBB的中心并不是刚刚计算出来的那个    , 一个简单的案例就是下边这个三角形: 

就这样我们就得到了一片Mesh的OBB,然而这种方法其实在有限元中用的不多,大家可以看到这个计算OBB的过程已经很繁琐了,然而其在游戏、CAD中用的还是不少的,但本着大家学就有用的思想,技多不压身,后面我可以单独讲一讲为啥他们在这些行业会有用。

另外就是大家可能会在别的地方看到不同的OBB计算方法,但是基本思想是一致的,但是OBB的计算方法如果展开说我可以直接开一系列,最常用的一个变形就是先计算一堆节点的凸包(convex hull)再用其来计算OBB,但既然我还没讲过convex hull先就此略过,以后再补。

然后还有更麻烦的呢,OBB的接触检测又需要用到一个算法,分离轴定理(SAT),其实也可以直接用GJK算法,这些也挺繁琐,有待下回分解。

参考文献:  

[1] Siddique, Abu Bakar, and Tariq A. Khraishi. “Eigenvalues and Eigenvectors for 3x3 Symmetric Matrices: An Analytical Approach.” Journal of Advances in Mathematics and Computer Science 35.7 (2020): 106-118


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

有限元基础知识:拟牛顿法与BFGS

上次说到了完全牛顿法与修正牛顿法,又基于修正牛顿法引入了线搜索相关的知识,有限元基础知识:牛顿法与修正牛顿法有限元基础知识:线搜索现在介绍另一类迭代方法,割线类迭代方法,也称之为拟牛顿法(Quasi-Newton)。对于割线类迭代方法,也就是这里说的拟牛顿法,核心思想也比比较简单,可以这么思考:完全牛顿法需要每个迭代步都计算切线刚度矩阵,费时费力修正牛顿法虽然规避了上述问题,但是迭代次数往往过多那么我们大概预估一下切线刚度矩阵吧首先我们在第j个迭代已经计算了切线刚度矩阵的基础上计算:注意这里的我们并没有更新到,那么基于我们计算出来的,我们可以执行如下计算,这里就与完全牛顿法不同,我们做一个假定:而其中可以看到这个和完全牛顿法中在等式右侧向量是完全不同的,我们将完全牛顿法中的替换成了。这样我们其实是构建出了与步之间的一个割线方程,其得出的刚度矩阵为类似于下图的割线方向:下面我们进行一些公式转换,可以得到多种“近似”的刚度矩阵更新办法,其中一阶形式为:但是观察上面的式子我们可以看到,其虽然可以无需进行重复的单元计算去更新刚度矩阵,而还是要进行麻烦的矩阵分解(往往最为耗时),就其实在一定程度上失去了意义。所以科学家们就提出了,直接更新刚度矩阵逆矩阵的方法,其中最出名的就是BFGS(Broyden–FletcherGoldfarb–Shanno)更新虽然上述公式看起来十分复杂,但是在真正的实现过程中,就避免了矩阵的重新计算与分解,很多vector与matrix的结果都可以储存起来用于更新,其实现过程也有很多细节,这部分则留到下次说。现在可以得到的结论是,由于其刚度矩阵是一直更新的,但又仅仅是近似的切线刚度矩阵,拿割线代替切线,那么其收敛性往往是弱于完全牛顿法的,但一般又好于修正牛顿法,一般来说相比完全牛顿法的二次收敛,我们往往称之为superlinear收敛性,意思就是收敛ratio&gt;1.0。总的来说这个方法的好处,就是以相对小的代价提供了接近切线刚度矩阵的刚度更新,所以往往计算效率都能提高。然而由于有限元分析的复杂性,有的时候割线刚度矩阵并不能很好的模拟切线,所以与其说其在有限元中用的多,近些年来BFGS这类方法反而是在优化领域用的更多。来源:大狗子说数值模拟

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