一个拥有3A游戏梦的不正经大学生|

shadow_lr

园龄:4年10个月粉丝:41关注:1

游戏中的2D OBB碰撞模型的碰撞算法介绍和实践

前言

上一篇博文说道,射线与场景中模型上的所有三角形求交时,会大幅度影响效率且花费比较多的时间,因此会采取使用包围盒的形式,进行一个加速求交。在此文中介绍OBB碰撞模型的碰撞算法

OBB的碰撞模型

有没有想过为什么需要用到OBB模型呢,假设一个场景内两个人物相撞了,你怎么判断它们是否相撞呢,大概就是它们的碰撞体接触在了一起就相撞了。那怎么算碰撞在一起呢(此处只讨论2D规则的包围盒模型)?
方向包围盒OBB"("Oriented Bounding Box)是目前比较流行的一种包围盒,OBB最大的特点是其方向的任意性,这使得可以根据被包围的对象的形状特点尽可能紧密地包围对象,Unity中的BoxCollider的其实就是OBB模型,它不是轴对称模型,而是有方向的
image.png

OBB模型
与之相反的是AABB模型,是轴对称模型,即它的边一定与坐标轴平行,算法简单,但使用的局限性比较大,更多还是使用OBB模型

image.png

碰撞算法分析

想要判断两个OBB模型碰撞,也就是两个矩形相交,我们分为几个步骤,首先先转换问题,什么时候两个矩形不相交,两个矩形相离可看成有多个直线可将它们之间分开。
当逐渐移动某个矩形,使得某个时刻,两个矩形只有一个交点,交点属于矩形的某条边上,此时为临界状态,当且仅当只有一条直线将他们两个分开,此时这条直线必定与某条边平行。
image.png

image.png
所以我们只需找两个矩形的四条边分别作为轴,两个矩形的xVt、yVt分别进行投影,看投影后的两个线段是否相离,如果相离则在这个轴上可以将这两个进行分开,故此时两个矩形不相交,反之若相交,则继续接着其他轴进行判断,若所有轴都不能分开,则这两个矩形相交
我们观察 AB proj 与 boxA、boxB 的 xVt proj 、yVt proj 之间的关系,可以得出结论:

AB proj > sum(Vt proj) ,则矩形相离
AB proj = sum(Vt proj) ,则矩形相切
AB proj < sum(Vt proj) ,则矩形相交

image.png

参考网上某大佬的代码

参考博文:https://www.cnblogs.com/hont/p/9501169.html

分别取两个矩形的两个边,总共进行四次投影对称,作为对称轴

        axis1 = (P1 - P0).normalized;
        axis2 = (P3 - P0).normalized;

        axis3 = (other.P1 - other.P0).normalized;
        axis4 = (other.P3 - other.P0).normalized;

        mDebugInternalAxisIndex = 0;

        bool isNotIntersect = false;
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis1);
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis2);
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis3);
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis4);

这里是取带符号的长度,用来比较投影后的线段是否相交

        float x_p0 = xProject_P0.magnitude * Mathf.Sign(Vector3.Dot(xProject_P0, axis));
        float x_p1 = xProject_P1.magnitude * Mathf.Sign(Vector3.Dot(xProject_P1, axis));
        float x_p2 = xProject_P2.magnitude * Mathf.Sign(Vector3.Dot(xProject_P2, axis));
        float x_p3 = xProject_P3.magnitude * Mathf.Sign(Vector3.Dot(xProject_P3, axis));

相交判断:

        if (yMin >= xMin && yMin <= xMax) return false;
        if (yMax >= xMin && yMax <= xMax) return false;

image.png

简约的示例图
using UnityEngine;
// OBB.cs
public class OBB : MonoBehaviour
{
    public bool enableDebug;
    public int debug_axisIndex;
    int mDebugInternalAxisIndex;

    public Vector2 size;

    public Color gizmosColor = Color.white;

    Vector2 P0 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(-size * 0.5f); } }
    Vector2 P1 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(size.x * 0.5f, -size.y * 0.5f, 0)); } }
    Vector2 P2 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(size * 0.5f); } }
    Vector2 P3 { get { return transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(-size.x * 0.5f, size.y * 0.5f, 0)); } }


    Vector2 axis1, axis2, axis3, axis4;

    // 较参考博文添加以下变量,用来缓存向量减少gc
    Vector3 xProject_P0;
    Vector3 xProject_P1;
    Vector3 xProject_P2;
    Vector3 xProject_P3;
    
    Vector3 yProject_P0;
    Vector3 yProject_P1;
    Vector3 yProject_P2;
    Vector3 yProject_P3;


    public bool Intersects(OBB other)
    {
        axis1 = (P1 - P0).normalized;
        axis2 = (P3 - P0).normalized;

        axis3 = (other.P1 - other.P0).normalized;
        axis4 = (other.P3 - other.P0).normalized;

        mDebugInternalAxisIndex = 0;

        bool isNotIntersect = false;
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis1);
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis2);
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis3);
        isNotIntersect |= ProjectionIsNotIntersect(this, other, axis4);

        return isNotIntersect ? false : true;
    }

    bool ProjectionIsNotIntersect(OBB x, OBB y, Vector2 axis)
    {
        xProject_P0 = Vector3.Project(x.P0, axis);
        xProject_P1 = Vector3.Project(x.P1, axis);
        xProject_P2 = Vector3.Project(x.P2, axis);
        xProject_P3 = Vector3.Project(x.P3, axis);

        float x_p0 = xProject_P0.magnitude * Mathf.Sign(Vector3.Dot(xProject_P0, axis));
        float x_p1 = xProject_P1.magnitude * Mathf.Sign(Vector3.Dot(xProject_P1, axis));
        float x_p2 = xProject_P2.magnitude * Mathf.Sign(Vector3.Dot(xProject_P2, axis));
        float x_p3 = xProject_P3.magnitude * Mathf.Sign(Vector3.Dot(xProject_P3, axis));

        yProject_P0 = Vector3.Project(y.P0, axis);
        yProject_P1 = Vector3.Project(y.P1, axis);
        yProject_P2 = Vector3.Project(y.P2, axis);
        yProject_P3 = Vector3.Project(y.P3, axis);

        float y_p0 = yProject_P0.magnitude * Mathf.Sign(Vector3.Dot(yProject_P0, axis));
        float y_p1 = yProject_P1.magnitude * Mathf.Sign(Vector3.Dot(yProject_P1, axis));
        float y_p2 = yProject_P2.magnitude * Mathf.Sign(Vector3.Dot(yProject_P2, axis));
        float y_p3 = yProject_P3.magnitude * Mathf.Sign(Vector3.Dot(yProject_P3, axis));

        float xMin = Mathf.Min(x_p0, x_p1, x_p2, x_p3);
        float xMax = Mathf.Max(x_p0, x_p1, x_p2, x_p3);
        float yMin = Mathf.Min(y_p0, y_p1, y_p2, y_p3);
        float yMax = Mathf.Max(y_p0, y_p1, y_p2, y_p3);

        if (enableDebug)
        {
            if (debug_axisIndex == mDebugInternalAxisIndex)
            {
                Debug.DrawRay(Vector3.Project(x.P0, axis), Vector3.one * 0.1f);
                Debug.DrawRay(Vector3.Project(x.P2, axis), Vector3.one * 0.1f);

                Debug.DrawRay(Vector3.Project(y.P0, axis), Vector3.one * 0.1f, Color.white * 0.9f);
                Debug.DrawRay(Vector3.Project(y.P2, axis), Vector3.one * 0.1f, Color.white * 0.9f);

                Debug.DrawRay(Vector3.zero, Vector3.one * 0.1f, Color.black);
                Debug.DrawRay(Vector3.zero, axis, Color.yellow);
                Debug.DrawRay(xMin * Vector3.right, Vector3.one * 0.1f, Color.blue);
                Debug.DrawRay(xMax * Vector3.right, Vector3.one * 0.1f, Color.cyan);
                Debug.DrawRay(yMin * Vector3.right, Vector3.one * 0.1f, Color.red * 0.5f);
                Debug.DrawRay(yMax * Vector3.right, Vector3.one * 0.1f, Color.red * 0.5f);

                Debug.Log("(yMin >= xMin && yMin <= xMax): " + (yMin >= xMin && yMin <= xMax) + " frame count: " + Time.frameCount);
                Debug.Log("(yMax >= xMin && yMax <= xMax): " + (yMax >= xMin && yMax <= xMax) + " frame count: " + Time.frameCount);
                Debug.Log("(xMin >= yMin && xMin <= yMax): " + (xMin >= yMin && xMin <= yMax) + " frame count: " + Time.frameCount);
                Debug.Log("(xMax >= yMin && xMax <= yMax): " + (xMax >= yMin && xMax <= yMax) + " frame count: " + Time.frameCount);
            }
            mDebugInternalAxisIndex++;
        }

        if (yMin >= xMin && yMin <= xMax) return false;
        if (yMax >= xMin && yMax <= xMax) return false;
        // 此处只需做两次判断即可,参考博文做了四次判断
        // if (xMin >= yMin && xMin <= yMax) return false;
        // if (xMax >= yMin && xMax <= yMax) return false;

        return true;
    }

    void OnDrawGizmos()
    {
        Gizmos.matrix = transform.localToWorldMatrix;
        Gizmos.color = gizmosColor;
        Gizmos.DrawWireCube(Vector3.zero, new Vector3(size.x, size.y, 1f));
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// OBBTest.cs
public class OBBTest : MonoBehaviour
{
    public OBB a;
    public OBB b;

    void Update()
    {
        var isIntersects = a.Intersects(b);
        if (isIntersects)
        {
            a.gizmosColor = Color.red;
            b.gizmosColor = Color.red;
        }
        else
        {
            a.gizmosColor = Color.white;
            b.gizmosColor = Color.white;
        }
    }
}

效果

Alt Text
Alt Text

本文作者:shadow_lr

本文链接:https://www.cnblogs.com/shadow-lr/p/OBBCollisionIntroduceAndAchieve.html

版权声明:本作品采用shadow-lr许可协议进行许可。

posted @   shadow_lr  阅读(1587)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起