busyoGG / busyoGG.github.io

1 stars 0 forks source link

碰撞检测之OBB - Busyo's Blog #12

Open busyoGG opened 7 months ago

busyoGG commented 7 months ago

https://busyo.buzz/article/3c9cb66ca768/

利用SAT分离轴检测实现OBB检测的原理 - Busyo - Busyo's Blog

Parano111d commented 6 months ago

请问除了射线类的 其他类的比如OBB和OBB的 怎么获得碰撞面的法线或者说碰撞点呢

busyoGG commented 6 months ago

@Parano111d 请问除了射线类的 其他类的比如OBB和OBB的 怎么获得碰撞面的法线或者说碰撞点呢

AABB之间判断xyz轴中深入距离最小的轴,两个包围盒最大值和最小值相减一下就能得到6个方向的深入距离,判断最小值就好了,这个轴就是碰撞面的法线

OBB之间原理和AABB差不多,每个分离轴都检测深入距离。一个OBB包围另一个的情况就判断里面OBB分离轴投影的边界和外面投影的边界距离最小的一边为深入距离;相交不包围就取投影相交距离最短的为深入距离。深入距离最小的那个分离轴就是碰撞面的法线

如果有出现距离相同的话就自己看怎么处理了

Parano111d commented 6 months ago

请问有源码吗 刚开始手撸物理 算法不精还有点抽象 用的是您unity碰撞检测的那个demo

---- Replied Message ---- | From | @.> | | Date | 02/19/2024 18:23 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 请问除了射线类的 其他类的比如OBB和OBB的 怎么获得碰撞面的法线或者说碰撞点呢

AABB之间判断xyz轴中深入距离最小的轴,两个包围盒最大值和最小值相减一下就能得到6个方向的深入距离,判断最小值就好了,这个轴就是碰撞面的法线

OBB之间原理和AABB差不多,每个分离轴都检测深入距离。一个OBB包围另一个的情况就判断里面OBB分离轴投影的边界和外面投影的边界距离最小的一边为深入距离;相交不包围就取投影相交距离最短的为深入距离。深入距离最小的那个分离轴就是碰撞面的法线

如果有出现距离相同的话就自己看怎么处理了

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

busyoGG commented 6 months ago

@Parano111d 请问有源码吗 刚开始手撸物理 算法不精还有点抽象 用的是您unity碰撞检测的那个demo

---- Replied Message ---- | From | @.> | | Date | 02/19/2024 18:23 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 请问除了射线类的 其他类的比如OBB和OBB的 怎么获得碰撞面的法线或者说碰撞点呢

AABB之间判断xyz轴中深入距离最小的轴,两个包围盒最大值和最小值相减一下就能得到6个方向的深入距离,判断最小值就好了,这个轴就是碰撞面的法线

OBB之间原理和AABB差不多,每个分离轴都检测深入距离。一个OBB包围另一个的情况就判断里面OBB分离轴投影的边界和外面投影的边界距离最小的一边为深入距离;相交不包围就取投影相交距离最短的为深入距离。深入距离最小的那个分离轴就是碰撞面的法线

如果有出现距离相同的话就自己看怎么处理了

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

//AABB获取法线
public static Vector3 GetCollideNormal(AABBData data1, AABBData data2, out float len)
{
    Vector3 normal = Vector3.zero;
    Vector3 len1_2 = data1.max - data2.min;
    Vector3 len2_1 = data2.max - data1.min;

    float[] depth = new float[6] {
        len1_2.x,
        len1_2.y,
        len1_2.z,
        len2_1.x,
        len2_1.y,
        len2_1.z
    };

    float min = depth[0];
    List<int> index = new List<int>() { 0 };
    for (int i = 1; i < depth.Length; i++)
    {
        if (depth[i] < min)
        {
            min = depth[i];
            if (index.Count > 1)
            {
                index.Clear();
            }

            if (index.Count == 0)
            {
                index.Add(i);
            }
            else
            {
                index[0] = i;
            }
        }
        else if (depth[i] == min)
        {
            index.Add(i);
        }
    }

    len = min;
    //判断法线方向
    for (int i = 0; i < index.Count; i++)
    {
        switch (index[i])
        {
            case 0:
                normal.x = -1;
                break;
            case 1:
                normal.y = -1;
                break;
            case 2:
                normal.z = -1;
                break;
            case 3:
                normal.x = 1;
                break;
            case 4:
                normal.y = 1;
                break;
            case 5:
                normal.z = 1;
                break;
        }
    }
    return normal.normalized;
}

//OBB获取碰撞法线
//有部分防止重复计算的内容,从其他项目拿来的,看着改吧
public static Vector3 GetCollideNormal(OBBData data1, OBBData data2, out float len)
{
    string id = data1.GetHashCode() + "-" + data2.GetHashCode();
    Vector3 normal = Vector3.zero;

    Vector3[] axes;
    _seperatingAxes.TryGetValue(id, out axes);
    List<Vector2[]> limitList;
    _limitObb.TryGetValue(id, out limitList);

    if (axes == null)
    {
        int len1 = data1.axes.Length;
        int len2 = data2.axes.Length;
        axes = new Vector3[len1 + len2 + len1 * len2];
        int k = 0;
        int initJ = len2;
        for (int i = 0; i < len1; i++)
        {
            axes[k++] = data1.axes[i];
            for (int j = 0; j < len2; j++)
            {
                if (initJ > 0)
                {
                    initJ--;
                    axes[k++] = data2.axes[j];
                }
                axes[k++] = Vector3.Cross(data1.axes[i], data2.axes[j]);
            }
        }
        _seperatingAxes.Add(id, axes);
    }

    if (limitList == null || limitList.Count != axes.Length)
    {
        if (limitList == null)
        {
            limitList = new List<Vector2[]>();
            _limitObb.Add(id, limitList);
        }
        else
        {
            limitList.Clear();
        }

        for (int i = 0; i < axes.Length; i++)
        {
            Vector2[] limit;
            bool isNotInteractive = NotInteractiveOBB(data1.vertexts, data2.vertexts, axes[i], out limit);

            if (isNotInteractive)
            {
                //有一个不相交就退出
                len = 0;
                return normal;
            }
            else
            {
                limitList.Add(limit);
            }
        }
    }

    float minOverlap = float.MaxValue;

    for (int i = 0; i < limitList.Count; i++)
    {
        if (axes[i].x == 0 && axes[i].y == 0 && axes[i].z == 0) continue;
        Vector2[] limit = limitList[i];
        float overlap;
        //看别人代码抄过来的
        //理论上第一个if和第二个else if应该是一样的,还没合并试过
        if (limit[0].y > limit[1].y && limit[0].x < limit[1].x)
        {
            overlap = Mathf.Min(limit[0].y - limit[1].x, limit[1].y - limit[0].x);
        }
        else if (limit[1].y > limit[0].y && limit[1].x < limit[0].x)
        {
            overlap = Mathf.Min(limit[1].y - limit[0].x, limit[0].y - limit[1].x);
        }
        else
        {
            overlap = Mathf.Min(limit[0].y, limit[1].y) - Mathf.Max(limit[0].x, limit[1].x);
        }
        if (overlap >= -0.001)
        {

            if (overlap < minOverlap)
            {
                minOverlap = overlap;
                normal = axes[i];
            }
        }
        else
        {
            len = 0;
            limitList.Clear();
            return normal;
        }
    }

    len = minOverlap / normal.magnitude;
    //len = minOverlap;
    Vector3 dis = data1.position - data2.position;
    float amount = normal.x * dis.x + normal.y * dis.y + normal.z * dis.z;
    if (amount < 0)
    {
        normal = -normal;
    }
    if (len > 0.5f)
    {
        ConsoleUtils.Log("超长");
    }
    return normal.normalized;
}

/// <summary>
/// 计算投影是否不相交
/// </summary>
/// <param name="vertexs1"></param>
/// <param name="vertexs2"></param>
/// <param name="axis"></param>
/// <returns></returns>
public static bool NotInteractiveOBB(Vector3[] vertexs1, Vector3[] vertexs2, Vector3 axis, out Vector2[] limit)
{
    limit = new Vector2[2];
    //计算OBB包围盒在分离轴上的投影极限值
    limit[0] = GetProjectionLimit(vertexs1, axis);
    limit[1] = GetProjectionLimit(vertexs2, axis);
    //两个包围盒极限值不相交,则不碰撞
    bool res = limit[0].x - limit[1].y >= 0.001 || limit[1].x - limit[0].y >= 0.001;
    return res;
}

/// <summary>
/// 计算顶点投影极限值
/// </summary>
/// <param name="vertexts"></param>
/// <param name="axis"></param>
/// <returns></returns>
public static Vector2 GetProjectionLimit(Vector3[] vertexts, Vector3 axis)
{
    Vector2 result = new Vector2(float.MaxValue, float.MinValue);
    for (int i = 0, len = vertexts.Length; i < len; i++)
    {
        Vector3 vertext = vertexts[i];
        float dot = Vector3.Dot(vertext, axis);
        result.x = Mathf.Min(dot, result[0]);
        result.y = Mathf.Max(dot, result[1]);
    }
    return result;
}

可以参考一下,不能保证百分百没错

Parano111d commented 6 months ago

好的!感谢大佬 有空我修改了测试一下

---- Replied Message ---- | From | @.> | | Date | 02/19/2024 18:37 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 请问有源码吗 刚开始手撸物理 算法不精还有点抽象 用的是您unity碰撞检测的那个demo

---- Replied Message ---- | From | @.> | | Date | 02/19/2024 18:23 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 请问除了射线类的 其他类的比如OBB和OBB的 怎么获得碰撞面的法线或者说碰撞点呢

AABB之间判断xyz轴中深入距离最小的轴,两个包围盒最大值和最小值相减一下就能得到6个方向的深入距离,判断最小值就好了,这个轴就是碰撞面的法线

OBB之间原理和AABB差不多,每个分离轴都检测深入距离。一个OBB包围另一个的情况就判断里面OBB分离轴投影的边界和外面投影的边界距离最小的一边为深入距离;相交不包围就取投影相交距离最短的为深入距离。深入距离最小的那个分离轴就是碰撞面的法线

如果有出现距离相同的话就自己看怎么处理了

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

//AABB获取法线publicstatic Vector3 GetCollideNormal(AABBDatadata1,AABBDatadata2,outfloatlen){Vector3normal= Vector3.zero;Vector3len1_2= data1.max - data2.min;Vector3len2_1= data2.max - data1.min;float[]depth=newfloat[6]{ len1_2.x, len1_2.y, len1_2.z, len2_1.x, len2_1.y, len2_1.z };floatmin= depth[0];Listindex=newList(){0};for(inti=1;i< depth.Length;i++){if(depth[i]<min){min= depth[i];if(index.Count >1){ index.Clear();}if(index.Count ==0){ index.Add(i);}else{ index[0]=i;}}elseif(depth[i]==min){ index.Add(i);}}len=min;//判断法线方向for(inti=0;i< index.Count;i++){switch(index[i]){case0: normal.x =-1;break;case1: normal.y =-1;break;case2: normal.z =-1;break;case3: normal.x =1;break;case4: normal.y =1;break;case5: normal.z =1;break;}}return normal.normalized;}//OBB获取碰撞法线//有部分防止重复计算的内容,从其他项目拿来的,看着改吧publicstatic Vector3 GetCollideNormal(OBBDatadata1,OBBDatadata2,outfloatlen){stringid= data1.GetHashCode()+"-"+ data2.GetHashCode();Vector3normal= Vector3.zero;

Vector3[]axes;
_seperatingAxes.TryGetValue(id,out axes);List<Vector2[]>limitList;
_limitObb.TryGetValue(id,out limitList);if(axes==null){intlen1= data1.axes.Length;intlen2= data2.axes.Length;axes=new Vector3[len1+len2+len1*len2];intk=0;intinitJ= len2;for(inti=0;i<len1;i++){
        axes[k++]= data1.axes[i];for(intj=0;j<len2;j++){if(initJ>0){initJ--;
                axes[k++]= data2.axes[j];}
            axes[k++]= Vector3.Cross(data1.axes[i], data2.axes[j]);}}
    _seperatingAxes.Add(id, axes);}if(limitList==null|| limitList.Count != axes.Length){if(limitList==null){limitList=newList<Vector2[]>();
        _limitObb.Add(id, limitList);}else{
        limitList.Clear();}for(inti=0;i< axes.Length;i++){
        Vector2[]limit;boolisNotInteractive= NotInteractiveOBB(data1.vertexts, data2.vertexts, axes[i],out limit);if(isNotInteractive){//有一个不相交就退出len=0;returnnormal;}else{
            limitList.Add(limit);}}}floatminOverlap=float.MaxValue;for(inti=0;i< limitList.Count;i++){if(axes[i].x ==0&& axes[i].y ==0&& axes[i].z ==0)continue;
    Vector2[]limit= limitList[i];floatoverlap;//看别人代码抄过来的//理论上第一个if和第二个else if应该是一样的,还没合并试过if(limit[0].y > limit[1].y && limit[0].x < limit[1].x){overlap= Mathf.Min(limit[0].y - limit[1].x, limit[1].y - limit[0].x);}elseif(limit[1].y > limit[0].y && limit[1].x < limit[0].x){overlap= Mathf.Min(limit[1].y - limit[0].x, limit[0].y - limit[1].x);}else{overlap= Mathf.Min(limit[0].y, limit[1].y)- Mathf.Max(limit[0].x, limit[1].x);}if(overlap >= -0.001){if(overlap<minOverlap){minOverlap=overlap;normal= axes[i];}}else{len=0;
        limitList.Clear();returnnormal;}}len=minOverlap/ normal.magnitude;//len = minOverlap;Vector3dis= data1.position - data2.position;floatamount= normal.x * dis.x + normal.y * dis.y + normal.z * dis.z;if(amount<0){normal=-normal;}if(len>0.5f){
    ConsoleUtils.Log("超长");}return normal.normalized;}/// <summary>/// 计算投影是否不相交/// </summary>/// <param name="vertexs1"></param>/// <param name="vertexs2"></param>/// <param name="axis"></param>/// <returns></returns>publicstaticboolNotInteractiveOBB(Vector3[]vertexs1, Vector3[]vertexs2,Vector3axis,out Vector2[]limit){limit=new Vector2[2];//计算OBB包围盒在分离轴上的投影极限值
limit[0]= GetProjectionLimit(vertexs1, axis);
limit[1]= GetProjectionLimit(vertexs2, axis);//两个包围盒极限值不相交,则不碰撞boolres= limit[0].x - limit[1].y >= 0.001|| limit[1].x - limit[0].y >= 0.001;returnres;}/// <summary>/// 计算顶点投影极限值/// </summary>/// <param name="vertexts"></param>/// <param name="axis"></param>/// <returns></returns>publicstatic Vector2 GetProjectionLimit(Vector3[]vertexts,Vector3axis){Vector2result=new Vector2(float.MaxValue,float.MinValue);for(inti=0,len= vertexts.Length;i<len;i++){Vector3vertext= vertexts[i];floatdot= Vector3.Dot(vertext, axis);
    result.x = Mathf.Min(dot, result[0]);
    result.y = Mathf.Max(dot, result[1]);}returnresult;}

可以参考一下,不能保证百分百没错

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

Parano111d commented 6 months ago

在修改并测试了您的代码以后,发现基本是可用的 但是在角色移动碰撞到障碍物的过程中,如果一直往障碍物的方向移动,还是有几率会穿透,出现代码中碰撞深度“超长”的状态,请问有办法处理吗

busyoGG commented 6 months ago

@Parano111d 在修改并测试了您的代码以后,发现基本是可用的 但是在角色移动碰撞到障碍物的过程中,如果一直往障碍物的方向移动,还是有几率会穿透,出现代码中碰撞深度“超长”的状态,请问有办法处理吗

碰撞之后要设置角色位置到碰撞前的地方,法线和深度就是用来做这个的

假设红色是碰撞时候的包围盒位置,碰撞检测到之后就把包围盒移动到蓝色的位置

Parano111d commented 6 months ago

对的 我目前就是这样做的 用角色预计位置+=碰撞法线(方向*深度),但还是会有几率穿透

Parano111d commented 6 months ago

好像发现了点端倪, 当物体A的正面和被碰撞物体B的受碰撞面平行的时候,就可以直接穿过去了 请问这是啥问题啊

busyoGG commented 6 months ago

@Parano111d 好像发现了点端倪, 当物体A的正面和被碰撞物体B的受碰撞面平行的时候,就可以直接穿过去了 请问这是啥问题啊

改成这样试试,换成碰撞深度在分离轴的占比进行比较

Parano111d commented 6 months ago

真的可以了!感谢大佬 大佬威武

---- Replied Message ---- | From | @.> | | Date | 02/20/2024 17:43 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 好像发现了点端倪, 当物体A的正面和被碰撞物体B的受碰撞面平行的时候,就可以直接穿过去了 请问这是啥问题啊

改成这样试试,换成碰撞深度在分离轴的占比进行比较

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

Parano111d commented 6 months ago

抱歉又打扰您了 我在使用这个碰撞法线的过程中 我发现两个碰撞体在紧挨并保持相对静止的时候,碰撞法线的方向会在正负之间来回切换 也就是方法中的碰撞深度len会在正负之间切换 请问这是什么原因

---- Replied Message ---- | From | @.> | | Date | 02/20/2024 17:43 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 好像发现了点端倪, 当物体A的正面和被碰撞物体B的受碰撞面平行的时候,就可以直接穿过去了 请问这是啥问题啊

改成这样试试,换成碰撞深度在分离轴的占比进行比较

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

busyoGG commented 6 months ago

@Parano111d 抱歉又打扰您了 我在使用这个碰撞法线的过程中 我发现两个碰撞体在紧挨并保持相对静止的时候,碰撞法线的方向会在正负之间来回切换 也就是方法中的碰撞深度len会在正负之间切换 请问这是什么原因

---- Replied Message ---- | From | @.> | | Date | 02/20/2024 17:43 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 好像发现了点端倪, 当物体A的正面和被碰撞物体B的受碰撞面平行的时候,就可以直接穿过去了 请问这是啥问题啊

改成这样试试,换成碰撞深度在分离轴的占比进行比较

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

如果是这样的小数字可能是浮点数的精度问题吧

我这基本上是正常的

具体情况我也不太清楚

Parano111d commented 6 months ago

好的 那我在看一下是不是浮点的精度问题 感谢了

---- Replied Message ---- | From | @.> | | Date | 02/22/2024 19:05 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 抱歉又打扰您了 我在使用这个碰撞法线的过程中 我发现两个碰撞体在紧挨并保持相对静止的时候,碰撞法线的方向会在正负之间来回切换 也就是方法中的碰撞深度len会在正负之间切换 请问这是什么原因

---- Replied Message ---- | From | @.> | | Date | 02/20/2024 17:43 | | To | @.> | | Cc | Sirui @.>, Mention @.> | | Subject | Re: [busyoGG/busyoGG.github.io] 碰撞检测之OBB - Busyo's Blog (Issue #12) |

@Parano111d 好像发现了点端倪, 当物体A的正面和被碰撞物体B的受碰撞面平行的时候,就可以直接穿过去了 请问这是啥问题啊

改成这样试试,换成碰撞深度在分离轴的占比进行比较

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>

如果是这样的小数字可能是浮点数的精度问题吧

我这基本上是正常的

具体情况我也不太清楚

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you were mentioned.Message ID: @.***>