OBB碰撞及四叉树优化

项目最近在移植lua,考虑到后续会用到帧同步,就自告奋勇,尝试写一下碰撞系统,原来实现过AABB的碰撞,OBB碰撞就是在AABB的碰撞上加了个旋转,可理解为有向的AABB(AABB为轴对其,可理解为在同一坐标系中,各碰撞的正方向轴都一样),下图为AABB与OBB区别

AABB与OBB区别
刚开始做的时候把这个碰撞想的太简单,没有经过深思熟虑,直接就开始写,结果导致越写越乱,越写东西越多,写代码还是得先设计!

AABB碰撞检测

1.AABB构造:
矩形包围盒可采用:
①中心点加宽高
②者对角线两个点。(这是小学还是初中数学知识记不清了,确定一个矩形的方法)。
方便更新的话,我采用了中心点加宽高的构造,代码中物体变了位置,就改变一个position就行(AABB的center默认为position)

球形包围盒:圆心center,半径R

2.AABB相交;
①矩形与矩形相交:
在这里插入图片描述
其中min与max 都是根据AABB自身计算出的数值

②矩形与球形相交:
找到球与矩形最近的一点,最近的一点都没有相交,则不相交。这个转换需要自己想一会,刚开始我也没想明白,想明白了就好了。
 if (other.Shape == ShapeType.Sphere)
            {
                ColliderSphere sphere = other as ColliderSphere;

                FixPoint x = sphere.Position.x;
                FixPoint y = sphere.Position.y;
                FixPoint z = sphere.Position.z;

                if (x > MaxX) { x = MaxX; }
                if (x < MinX) { x = MinX; }

                if (y > MaxY) { y = MaxY; }
                if (y < MinY) { y = MinY; }

                if (z > MaxZ) { z = MaxZ; }
                if (z < MinZ) { z = MinZ; }

                FixPoint distance = (x - sphere.Position.x) * (x - sphere.Position.x) + 
                    (y - sphere.Position.y) * (y - sphere.Position.y) + 
                    (z - sphere.Position.z) * (z - sphere.Position.z);

                if (sphere.Radius * sphere.Radius >= distance)
                {
                    return true;
                }
                return false;
            }

③圆与圆相交:无需多言

if(other.Shape == ShapeType.Sphere)
            {
                Vector3FP other_pos = other.Position;
                return Position.Distance(ref other_pos) <= Radius + (other as ColliderSphere).Radius;
            }

AABB相交到此结束

OBB构造:

球旋转后还是自身,当做没旋转

长方体构造:
①8个顶点
②6个面
③3组平行面
④一个顶点和三个彼此正交的变相量
⑤中心点,旋转矩阵,3个1/2边长

一般都采用第五种构造方式,因为计算比较方便,但是内存也要考虑,一般都是储存欧拉角或者四元数代替旋转矩阵,在做相交测试时,又得转换回矩阵,得不偿失

一种较好的妥协方式是:只存储旋转矩阵中的两个轴,只是在测试时利用叉积计算第三个轴,相对来讲,能够降低CPU的操作开销,还能节省三个浮点数分量,降低了百分之20的内存消耗
–《实时检测碰撞算法技术》第4.4章

OBB相交:

抽象为分离轴测试,分离轴测试:对于某一轴L,如果两盒体投影半径之和小于中心点之间的投影距离,则OBB处于分离状态
在这里插入图片描述下面列出c#代码(刚开始做的时候没有想太多,在网上扒了一份,自己得代码就不贡献了,我没有共享精神,写这个博客也记录一下这个工作).

public class ObbNew : MonoBehaviour
{
    [HideInInspector]
    public Transform m_Transform;
    [HideInInspector]
    public BoxCollider m_BoxCollider;
    [HideInInspector]
    private float m_Rotation;
    [HideInInspector]
    public Vector2 m_Extents;
    [HideInInspector]
    public Vector2[] m_Axiss;

  

    void Start()
    {
        m_Transform = this.transform;
        m_BoxCollider = m_Transform.GetComponent<BoxCollider>();
        m_Axiss = new Vector2[2];
        SetExtents();
    }

    // Update is called once per frame
    void Update()
    {
        m_Rotation = m_Transform.eulerAngles.y * Mathf.PI / 180;

        //两个轴
        m_Axiss[0] = new Vector2((float)Mathf.Cos(m_Rotation), -(float)Mathf.Sin(m_Rotation));  
        m_Axiss[1] = new Vector2(Mathf.Sin(m_Rotation), Mathf.Cos(m_Rotation));

    }

    private void SetExtents()
    {
        Quaternion rotation = m_Transform.rotation;
        m_Transform.rotation = new Quaternion(0, 0, 0, 1);

        Vector3 center = m_BoxCollider.center;
        Vector3 size = m_BoxCollider.size / 2;

        Vector3 Point1 = new Vector3(center.x + size.x, center.y, center.z - size.z);
        Vector3 Point2 = new Vector3(center.x - size.x, center.y, center.z + size.z);


        Point1 = m_Transform.TransformPoint(Point1);
        Point2 = m_Transform.TransformPoint(Point2);

        m_Extents = new Vector2(Mathf.Abs(Point1.x - Point2.x) / 2, Mathf.Abs(Point2.z - Point1.z) / 2);
        m_Transform.rotation = rotation;


    }

    public float dot(Vector2 a, Vector2 b)
    {
        return Mathf.Abs(a.x * b.x + a.y * b.y);
    }
    public float getProjectionRadius(Vector2 axis)
    {
        return (m_Extents.x * dot(m_Axiss[0], axis) + m_Extents.y * dot(m_Axiss[1], axis));
    }

    public bool intersects(ObbNew other)
    {
        Update();
        other.Update();
        Vector2 distanceVector = new Vector2(m_Transform.position.x - other.m_Transform.position.x, m_Transform.position.z - other.m_Transform.position.z);

        Vector2[] checkObbVector2s =
        {
            m_Axiss[0],
            m_Axiss[1],
            other.m_Axiss[0],
            other.m_Axiss[1],

           
        };
  
        for (int index = 0; index < checkObbVector2s.Length; index++)
        {
            Vector2 curVector2 = checkObbVector2s[index];
            if ((getProjectionRadius(curVector2) + other.getProjectionRadius(curVector2)) <= dot(distanceVector, curVector2))
            {
                return false;
            }
        }
        return true;
    }



}

这是能跑的代码,直接拉过来一个cube加个BoxCollider就能跑了,不像别的博客,代码都是烂的(吐槽一下大多数博客,代码都不能运行你在那分享啥呢,误人子弟。我被坑过不少次

球与OBB的碰撞 与上述的圆与矩形碰撞相似,不同的是将获取圆上离矩形最近一点改为球上与OBB
距离最近的一点
下面是伪代码

//给定点p,返回点q在(或在)OBB b上,最接近p
Vector2 ClosedPtPointToOBB(Point p,OBB b)
{
Vector q 
Vector2 d = p -b.center

q = b.center
//2d只考虑x,y  3d则改为3个轴
//遍历 OBB的 轴
for(i = 0; i< 2;i++)
{
//把d投影到轴上,得到距离
//从盒子中心沿d轴
float dis = Vector2.Dot(d,b.u[i])
If(dis >b.e[i] )
  Dis = b.e[i]
If(dis < -b.e[i])
dis = -b.e[i]
沿轴移动该距离以获得世界坐标
q +=dis * b.u[i]

}
return q
}

Test SphereOBB(Sphere s, OBB b )
{
Vector2 p = ClosedPtPointToOBB(s.center,b)
Vector2 v = p - s.center

Return Mathf.Dot(v,v) <=s.radius * s.radius
}

碰撞到此结束,主要是数学知识,从毕业一直写业务逻辑,数学知识都还给老师们了,写的我相当难受,用进废退,以后没事还是得看看数学,不能做一个沉迷于历史的程序员
好好学数学,说不定去做引擎了呢(微博狗头表情)

下面是四叉树,为何要用四叉树,上学的时候学这种枯燥的数据结构,一直提不起兴趣,当然,也没好好学,什么狗屁二叉树,叉你二大爷啊叉,有啥叉的,就分裂呗,为啥分裂,不知道,为啥要有二叉树,不知道,反正是感觉用不上,就没太在意过,然而为了应付不挂科,也稍微留了点意。

不是代码写完了能用了功能就完成了,跑起来就蒙蔽了,场景里500个墙,一发子弹打出去,每帧遍历循环这么多次,这还只是一发子弹,要是都动起来,这帧率,用不了,用不了。等于没写代码。

想了想,果然***前人总结的数据结构、算法、设计模式都不是白给的,都是趟过的坑***。

四叉树优化空间探测。不需要考虑物体的数量,只需考虑树的深度。时间复杂度这个概念也一下在我心里高大了起来

四叉树的教程有很多,我就不列出来了。

我的方案:
1.游戏初始化构造四叉树,深度定为8-10(综合项目地图和物体,这个深度需要根据不同情况变化)
2.实例化物体碰撞时,同步添加一份碰撞信息到树中。
3随着物体增多,树不断分裂
4.物体移动时,更新所在区域
5.检测碰撞时,找到这个物体可能碰撞的其余物体,四叉树递归
6.根据物体标签再进一步筛选可碰撞的list
7.碰撞检测.

总结:

1.写代码之前不设计,整个实现过程很慢,很浪费时间,如果在前***期设计规划一下,会很快的实现***。
2.数学呀!数学!
3.代码写完要优化,或者写的时候就要考虑到优化。我一直不信邪,一直被告诫c#和lua相互传递会很浪费效率,我就不以为意。直到500个碰撞体一起检测那一刹那,我的帧率快爆了在这里插入图片描述
在这里插入图片描述
就一个get_position就这么酸爽!

Entity通过Proto数据创建碰撞体,不要访问GameObject,碰撞体有自身相对的长宽高数据,再根据LogicTransform的Position,Rotation算出对应的顶点

相机也有与GameObject位置交互的地方,后续考虑还是得挪回到c#那里写,方案待定

发布了1 篇原创文章 · 获赞 0 · 访问量 13

猜你喜欢

转载自blog.csdn.net/qq_31107675/article/details/105576970