• Cocos2d-x 3D渲染技术 (三)


    包围盒算法

    说白了就是给物体装进一个盒子里,该盒子可以装下物体。目的是为了进行碰撞检测

    种类:

    1. 球状碰撞体
    2. 立方体碰撞体
    3. 胶囊碰撞体
    4. Mesh碰撞体

    实现原理是OBB包围盒。

    经常使用的两种碰撞算法是OBB包围盒和AABB包围盒算法。

    OBB包围盒算法

    方向包围盒(Oriented bounding box),简称为OBB。

    OBB包围盒特点:始终沿着物体的主方向生成最小的一个矩形包围盒,并且可以随物体旋转,这就决定了它可以用来做精准的碰撞检测。

    判断碰撞条件:两个多边形,在所有轴的投影都发生了重叠,则认为发生了碰撞;否则,认为没有发生碰撞。

    OBB有很多中表达的方式。

    cocos2dx中定义的表达方式为:

    Vec3 _center;   // obb center
    Vec3 _xAxis;    // x axis of obb, unit vector
    Vec3 _yAxis;    // y axis of obb, unit vector
    Vec3 _zAxis;    // z axis of obb, unit vector
    Vec3 _extentX;  // _xAxis * _extents.x
    Vec3 _extentY;  // _yAxis * _extents.y
    Vec3 _extentZ;  // _zAxis * _extents.z
    Vec3 _extents;  // obb length along each axis
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    _center :中心点

    _xAxis :包围盒X轴方向单位矢量

    _yAxis :包围盒Y轴方向单位矢量

    _zAxis :包围盒Z轴方向单位矢量

    _xAxis、_yAxis、_zAxis 为正交单位向量,定义了当前OBB包围盒的x,y,z轴,用来计算矢量投影的

    _extentX :包围盒X轴坐标

    _extentY :包围盒Y轴坐标

    _extentZ :包围盒Z轴坐标

    _extents :定义了OBB包围盒的长、宽、高

    这里可以看出来,每一个OBB包围盒需要24个float类型的变量来存储,占了96个字节。这样相比AABB来说,的确会需要额外的内存空间。

    减少开销的办法:只存储旋转矩阵的两个轴,只是在测试的时候,利用叉积计算第三个轴。

    创建 OBB

    cocos2dx 使用了两种创建方式

    利用AABB

    由一个AABB包围盒来确定最终OBB包围盒。

    OBB::OBB(const AABB& aabb)
    {
        reset();
        
        _center = (aabb._min + aabb._max);
        _center.scale(0.5f);
        _xAxis.set(1.0f, 0.0f, 0.0f);
        _yAxis.set(0.0f, 1.0f, 0.0f);
        _zAxis.set(0.0f, 0.0f, 1.0f);
        
        _extents = aabb._max - aabb._min;
        _extents.scale(0.5f);
        
        computeExtAxis();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    协方差矩阵

    利用协方差矩阵来确定一个方向包围盒。

    通俗点就是将原基下的坐标转换到新基上,然后依照新基算出AABB包围盒,然后再转换到原基,这个时候就是原基的OBB包围盒了。

    关于协方差和PCA的知识点可以参考:

    PCA

    里面讲得非常详细。

    在这里插入图片描述

    大部分引擎都使用到了该算法,cocos2dx中也使用了这个算法。如下:

    //生成协方差矩阵
    
    static Mat4 _getConvarianceMatrix(const Vec3* vertPos, int vertCount)
    {
        int i;
        Mat4 Cov;
    
        double S1[3];
        double S2[3][3];
    
        S1[0] = S1[1] = S1[2] = 0.0;
        S2[0][0] = S2[1][0] = S2[2][0] = 0.0;
        S2[0][1] = S2[1][1] = S2[2][1] = 0.0;
        S2[0][2] = S2[1][2] = S2[2][2] = 0.0;
    
        // get center of mass
        for(i=0; i<vertCount; i++)
        {
            S1[0] += vertPos[i].x;
            S1[1] += vertPos[i].y;
            S1[2] += vertPos[i].z;
    
            S2[0][0] += vertPos[i].x * vertPos[i].x;
            S2[1][1] += vertPos[i].y * vertPos[i].y;
            S2[2][2] += vertPos[i].z * vertPos[i].z;
            S2[0][1] += vertPos[i].x * vertPos[i].y;
            S2[0][2] += vertPos[i].x * vertPos[i].z;
            S2[1][2] += vertPos[i].y * vertPos[i].z;
        }
    
        float n = (float)vertCount;
        // now get covariances
        Cov.m[0] = (float)(S2[0][0] - S1[0]*S1[0] / n) / n;
        Cov.m[5] = (float)(S2[1][1] - S1[1]*S1[1] / n) / n;
        Cov.m[10] = (float)(S2[2][2] - S1[2]*S1[2] / n) / n;
        Cov.m[4] = (float)(S2[0][1] - S1[0]*S1[1] / n) / n;
        Cov.m[9] = (float)(S2[1][2] - S1[1]*S1[2] / n) / n;
        Cov.m[8] = (float)(S2[0][2] - S1[0]*S1[2] / n) / n;
        Cov.m[1] = Cov.m[4];
        Cov.m[2] = Cov.m[8];
        Cov.m[6] = Cov.m[9];
    
        return Cov;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    碰撞检测

    关于碰撞检测,在另一篇中有提到,利用分离轴定理(separating axis theorem) 来进行检测。

    cocos2dx 3D物理相关知识点汇总

    cocos2dx中相关代码:

    //将点映射到坐标上
    float OBB::projectPoint(const Vec3& point, const Vec3& axis)const
    {
        //点积的方式
        float dot = axis.dot(point);
        float ret = dot * point.length();
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    //计算 最大、最小的投影值
    void OBB::getInterval(const OBB& box, const Vec3& axis, float &min, float &max)const
    {
        Vec3 corners[8];
        box.getCorners(corners);
        float value;
        min = max = projectPoint(axis, corners[0]);
        for(int i = 1; i < 8; i++)
        {
            value = projectPoint(axis, corners[i]);
            min = MIN(min, value);
            max = MAX(max, value);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    //取边的矢量
    Vec3 OBB::getEdgeDirection(int index)const
    {
        Vec3 corners[8];
        getCorners(corners);
        
        Vec3 tmpLine;
        switch(index)
        {
            case 0:// edge with x axis
                tmpLine = corners[5] - corners[6];
                tmpLine.normalize();
                break;
            case 1:// edge with y axis
                tmpLine = corners[7] - corners[6];
                tmpLine.normalize();
                break;
            case 2:// edge with z axis
                tmpLine = corners[1] - corners[6];
                tmpLine.normalize();
                break;
            default:
                CCASSERT(0, "Invalid index!");
                break;
        }
        return tmpLine;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    // 取面的方向矢量
    Vec3 OBB::getFaceDirection(int index) const
    {
        Vec3 corners[8];
        getCorners(corners);
        
        Vec3 faceDirection, v0, v1;
        switch(index)
        {
            case 0:// front and back
                v0 = corners[2] - corners[1];
                v1 = corners[0] - corners[1];
                Vec3::cross(v0, v1, &faceDirection);
                faceDirection.normalize();
                break;
            case 1:// left and right
                v0 = corners[5] - corners[2];
                v1 = corners[3] - corners[2];
                Vec3::cross(v0, v1, &faceDirection);
                faceDirection.normalize();
                break;
            case 2:// top and bottom
                v0 = corners[1] - corners[2];
                v1 = corners[5] - corners[2];
                Vec3::cross(v0, v1, &faceDirection);
                faceDirection.normalize();
                break;
            default:
                CCASSERT(0, "Invalid index!");
                break;
        }
        return faceDirection;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    利用分离轴定理判断两个OBB是否碰撞

    第一个包围盒的3个坐标轴、第二个包围盒的3个坐标轴,以及垂直于某一个轴的9个轴。(一共是15个轴)

    垂直于某一轴的9个轴 : 取包围盒A的X轴方向的某一边矢量(不是X坐标轴,是x轴方向边的矢量),再取包围盒B的X轴方向的某一边矢量,对两个矢量做叉乘,叉乘结果就是垂直于A和B的矢量的方向矢量,这就是一个分离轴。如此重复,用包围盒A的X轴方向的某一边矢量依次叉乘包围盒B的X,Y,Z,然后再用包围盒A的Y和Z轴方向的某一边矢量再进行这一遍流程,总共得到9个轴。

    bool OBB::intersects(const OBB& box) const
    {
        float min1, max1, min2, max2;
        for (int i = 0; i < 3; i++)
        {
            getInterval(*this, getFaceDirection(i), min1, max1);
            getInterval(box, getFaceDirection(i), min2, max2);
            if (max1 < min2 || max2 < min1) return false;
        }
        
        for (int i = 0; i < 3; i++)
        {
            getInterval(*this, box.getFaceDirection(i), min1, max1);
            getInterval(box, box.getFaceDirection(i), min2, max2);
            if (max1 < min2 || max2 < min1) return false;
        }
        
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                Vec3 axis;
                Vec3::cross(getEdgeDirection(i), box.getEdgeDirection(j), &axis);
                getInterval(*this, axis, min1, max1);
                getInterval(box, axis, min2, max2);
                if (max1 < min2 || max2 < min1) return false;
            }
        }
        
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    //检查点是否在OBB中
    bool OBB::containPoint(const Vec3& point) const
    {
        Vec3 vd = point - _center;
    
        float d = vd.dot(_xAxis);
        if (d > _extents.x || d < -_extents.x)
            return false;
    
        d = vd.dot(_yAxis);
        if (d > _extents.y || d < -_extents.y)
            return false;
    
        d = vd.dot(_zAxis);
        if (d > _extents.z || d < -_extents.z)
            return false;
    
        return true;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    AABB包围盒算法

    轴对称包围盒。

    cocos2dx 中,为每一个Spite3D都提供了获取AABB包围盒的接口。

    记得转换一次,因为获取的AABB包围盒的坐标系是自己,所以需要转换到世界坐标上。

    const AABB& Sprite3D::getAABB() const
    {
        Mat4 nodeToWorldTransform(getNodeToWorldTransform());
        
        // If nodeToWorldTransform matrix isn't changed, we don't need to transform aabb.
        if (memcmp(_nodeToWorldTransform.m, nodeToWorldTransform.m, sizeof(Mat4)) == 0 && !_aabbDirty)
        {
            return _aabb;
        }
        else
        {
            _aabb.reset();
            if (_meshes.size())
            {
                Mat4 transform(nodeToWorldTransform);
                for (const auto& it : _meshes) {
                    if (it->isVisible())
                        _aabb.merge(it->getAABB());
                }
                
                _aabb.transform(transform);
                _nodeToWorldTransform = nodeToWorldTransform;
                _aabbDirty = false;
            }
        }
        
        return _aabb;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    其他

    碰撞算法用得最多的就是AABB和OBB,但也有其他的算法。比如Box2D,Bullet都有自己的一系列算法和检测机制。

  • 相关阅读:
    uniapp初步搭建:如何引入uview库(跨移动多端ui库)
    Pytest+Unittest+Git+Jenkins企业级CICD自动化测试平台建设方案
    【MATLAB】布朗运动动画仿真
    IP网络通信的单播、组播和广播
    个人简介网页设计作业 静态HTML个人介绍网页作业 DW个人网站模板下载 WEB静态大学生简单网页 个人网页作品代码 个人网页制作 学生个人网页
    基于Unity3D的PC&Android端2D横屏冒险类闯关游戏
    网络原理---网络初识
    如何学好机器学习?机器学习一定需要下面这几方面知识!
    7-34 通讯录的录入与显示
    【数据结构】带头双向循环链表
  • 原文地址:https://blog.csdn.net/canjiangmengyuan/article/details/128117008