首页 > 其他分享 >2d物理引擎学习 - 解决接触面穿透问题

2d物理引擎学习 - 解决接触面穿透问题

时间:2024-01-14 23:35:38浏览次数:35  
标签:穿透 collisionPair Vector2 float 2d contact rigidbodyB 接触面 rigidbodyA

就是一个物体掉落到另一个物体表面时,相互穿透,部分区域发生了重叠:

  

 

原因

现实世界,时间是连续的;但在计算机中,时间是离散的,帧与帧之间的时间间隔,没法保证两物体发生碰撞时,正好是表面刚接触。

比如:物体1和物体2相距6米,大小均为1x1,物体1向右以10米/s速度移动,物体2静止,0.5s后物体1和物体2的边缘正好刚发生接触。

同样的时间间隔,如果物体1是以12米/s的速度向右移动的,那0.5s后物体1和物体2就会有1米的穿透距离(重叠区域)。

 

解决办法

碰撞时,检查是否发生穿透,发生穿透我们就加一个修正用的速度让它们分离开。

这个速度用多大呢?v = 穿透距离 / 帧间隔时间

同时,为了防止用力过大,我们会加一个百分比参数来控制修正的程度:v = 修正程度 * 穿透距离 / 帧间隔时间

物理引擎中常用的方法叫:Baumgarte Stabilization

 

变化代码

//碰撞点信息
public class ContactInfo
{
    public Vector2 m_Point; //碰撞点
    public Vector2 m_Normal; //碰撞法向量(分离方向), 这边用A指向B, 即: B反弹方向
    public float m_Penetration; //穿透深度(分离距离)
    public uint m_Fp; //用于识别两box的碰撞

    public float m_ImpulseNormal; //法线方向累加冲量
    public float m_ImpulseTangent; //切线方向累加冲量

    public float m_MassNormal; //碰撞后速度计算公式中会用到: 1/m1+1/m2, 这里存放的就是这个结果
    public float m_MassTangent;

    public float m_Bias; //穿透修正速度
}

 

MyPhysics的PreSeperation和PostSeperation变化了少量代码

private void PreSeperation(float dt, CollisionPair collisionPair)
{
    const float allowedPenetration = 0.01f;
    const float biasFactor = 0.2f;
    float invDt = 1 / dt;

    var rigidbodyA = collisionPair.m_RigidbodyA;
    var rigidbodyB = collisionPair.m_RigidbodyB;

    for (int i = 0; i < collisionPair.m_NumContacts; ++i)
    {
        var contact = collisionPair.m_Contacts[i];
        Vector2 ra = contact.m_Point - rigidbodyA.Position;
        Vector2 rb = contact.m_Point - rigidbodyB.Position;

        Vector2 normal = contact.m_Normal;
        Vector2 tangent = new Vector2(normal.y, -normal.x); //切线(顺时针)

        float kMassNormal = rigidbodyA.InvMass + rigidbodyB.InvMass;
        float raN = Vector2.Dot(ra, normal);
        float rbN = Vector2.Dot(rb, normal);
        kMassNormal += rigidbodyA.InvInertia * (Vector2.Dot(ra, ra) - raN * raN) + rigidbodyB.InvInertia * (Vector2.Dot(rb, rb) - rbN * rbN);            
        contact.m_MassNormal = 1 / kMassNormal;

        float kMassTangent = rigidbodyA.InvMass + rigidbodyB.InvMass;
        float raT = Vector2.Dot(ra, tangent);
        float rbT = Vector2.Dot(rb, tangent);
        kMassTangent += rigidbodyA.InvInertia * (Vector2.Dot(ra, ra) - raT * raT) + rigidbodyB.InvInertia * (Vector2.Dot(rb, rb) - rbT * rbT);
        contact.m_MassTangent = 1 / kMassTangent;

        contact.m_Bias = -biasFactor * invDt * Mathf.Min(0, contact.m_Penetration + allowedPenetration);

        if (m_AccumulateImpulses)
        {
            Vector2 impulse = contact.m_ImpulseNormal * normal; //冲量大小转成冲量向量
            impulse += contact.m_ImpulseTangent * tangent; //切线方向

            rigidbodyA.ApplyImpulse(-impulse);
            rigidbodyA.ApplyTorqueImpulse(ra, -impulse);

            rigidbodyB.ApplyImpulse(impulse);
            rigidbodyB.ApplyTorqueImpulse(rb, impulse);
        }
    }
}


private void PostSeperation(float dt, CollisionPair collisionPair)
{
    var rigidbodyA = collisionPair.m_RigidbodyA;
    var rigidbodyB = collisionPair.m_RigidbodyB;
    float mergeFriction = Mathf.Sqrt(rigidbodyA.Friction * rigidbodyB.Friction);

    for (int i = 0; i < collisionPair.m_NumContacts; ++i)
    {
        var contact = collisionPair.m_Contacts[i];
        Vector2 ra = contact.m_Point - rigidbodyA.Position;
        Vector2 rb = contact.m_Point - rigidbodyB.Position;
        var relativeV = rigidbodyB.GetPointVelocity(rb) - rigidbodyA.GetPointVelocity(ra);

        var normal = contact.m_Normal;
        float relativeVN = Vector2.Dot(relativeV, normal); //投影到法向量
        //if (relativeVN > 0) //相对速度>0时, 表明没有碰撞趋势了
        //    return;

        //Δp = (1 + e) * (v2 - v1) / kMass
        float deltaPN = (-relativeVN + contact.m_Bias) * contact.m_MassNormal;

        if (m_AccumulateImpulses)
        {float lastImpulseN = contact.m_ImpulseNormal;
            contact.m_ImpulseNormal += deltaPN; //叠加本次冲量(冲量=Δp)
            if (contact.m_ImpulseNormal <= 0) //防止弹开过程中, 变成拉回来的冲量
            {
                contact.m_ImpulseNormal = 0;
                deltaPN = -lastImpulseN;
            }
        }
        else
        {
            deltaPN = Mathf.Max(deltaPN, 0); //冲量为负, 碰撞后就加速了, 这样不对
        }

        Vector2 impulseN = deltaPN * normal; //转为矢量
        rigidbodyA.ApplyImpulse(-impulseN);
        rigidbodyA.ApplyTorqueImpulse(ra, -impulseN);

        rigidbodyB.ApplyImpulse(impulseN);
        rigidbodyB.ApplyTorqueImpulse(rb, impulseN);

        //摩擦力(切线方向)
        relativeV = rigidbodyB.GetPointVelocity(rb) - rigidbodyA.GetPointVelocity(ra); //上面的冲量生效后, 再计算相对速度

        var tangent = new Vector2(normal.y, -normal.x); //切线(顺时针)
        float relativeVT = Vector2.Dot(relativeV, tangent); //投影到切线方向
        relativeVT = -relativeVT; //取反让值为正(与运动方向相同)
        float deltaPT = relativeVT * contact.m_MassTangent;

        if (m_AccumulateImpulses)
        {float maxDeltaPt = mergeFriction * contact.m_ImpulseNormal; //不能超过法向量的冲量
            float lastImpulseT = contact.m_ImpulseTangent;
            contact.m_ImpulseTangent = Mathf.Clamp(contact.m_ImpulseTangent + deltaPT, -maxDeltaPt, maxDeltaPt);
            deltaPT = contact.m_ImpulseTangent - lastImpulseT;
        }
        else
        {
            float maxDeltaPt = mergeFriction * deltaPN;
            deltaPT = Mathf.Clamp(deltaPT, -maxDeltaPt, maxDeltaPt);
        }

        Vector2 impulseT = deltaPT * tangent;
        rigidbodyA.ApplyImpulse(-impulseT); //摩擦力与运动(趋势)方向相反
        rigidbodyA.ApplyTorqueImpulse(ra, -impulseT);

        rigidbodyB.ApplyImpulse(impulseT);
        rigidbodyB.ApplyTorqueImpulse(rb, impulseT);
    }
}

 

参考

2D 游戏物理引擎 - 碰撞约束 - 知乎 (zhihu.com)

从零手写游戏引擎24:物理引擎ResolvePhase - 知乎 (zhihu.com)

 

标签:穿透,collisionPair,Vector2,float,2d,contact,rigidbodyB,接触面,rigidbodyA
From: https://www.cnblogs.com/sailJs/p/17929658.html

相关文章

  • 2d物理引擎学习 - 斜坡上下滑的物体
    效果 代码只是在之前的基础上增加了重力和摩擦力,重力的实现就是给物体加一个持续的力(即:Fg=m*g),摩擦力就是切线方向加一个修正冲量。 代码和之前的主要区别1) 刚体MyRigidbody增加一个Friction,摩擦系数属性2)MyRigidbody.PostSeperation中增加切线方向的冲量privat......
  • 2d物理引擎学习 - 基于约束的公式解决接触稳定性问题
    先看下直接用弹性碰撞的公式,会出现的问题:Box落在地面上后,没有停在地面上,而是还在不断的下沉。弹性碰撞公式处理碰撞后弹开没有大问题,但是处理物体碰撞后的接触存在不稳定问题。 如何解决?目前物理引擎最主流的解决方法是:基于约束来组织物理公式,而不是直接套用物理公式。什......
  • Cocos Creator 3.8 开发2D水面波纹Shader
     使用cocosCreator3.8做了一个游戏开中常用的2D的波浪水面,把技术点给记录一下,并提供完整的Shader代码。先上效果:   2D波浪的基本技术原理 2D水面波纹的主要原理就是给定一个正选波的边界,在范围内的片元uv就显示,在范围外的片元uv就不显示。同时利用正弦波表达式:......
  • 开源云原生网关Linux Traefik本地部署结合内网穿透远程访问
     开源云原生网关LinuxTraefik本地部署结合内网穿透远程访问前言Træfɪk是一个云原生的新型的HTTP反向代理、负载均衡软件,能轻易的部署微服务。它支持多种后端(Docker,Swarm,Mesos/Marathon,Consul,Etcd,Zookeeper,BoltDB,RestAPI,file…),可以对配置进行......
  • 请将鼠标悬停在下面的元素上,即可查看 2D 和 3D 转换之间的区别
    请将鼠标悬停在下面的元素上,即可查看2D和3D转换之间的区别:2Drotate3Drotatetransformcode<div><p>请将鼠标悬停在下面的元素上,即可查看2D和3D转换之间的区别:</p><style>#rotate2D,#rotate3D{width:80px;height:70px;color:white;......
  • 讲解'BatchNorm2d' object has no attribute 'track_running_stats'
    讲解'BatchNorm2d'objecthasnoattribute'track_running_stats'在使用深度学习框架PyTorch进行模型训练时,有时可能会遇到以下错误提示:plaintextCopycode'BatchNorm2d'objecthasnoattribute'track_running_stats'这个错误提示通常与PyTorch版本升级或代码中的一些配......
  • Sovit2D云组态平台 零代码快速构建组态界面
    随着工业4.0、互联网+、智能制造、云计算和大数据等技术的发展,一方面制造工控行业对基于异地异构网络控制设备上云集成的需求越来越高,另一方面移动互联网的普及让用户随时随地监控管理设备系统运行状态成为可能。传统的仅支持PC端的C/S模式组态软件正逐步被新型基于云端模式B/S架构......
  • 用SOFT ETHER配置穿透远程访问网络
    要使用SoftEther配置穿透远程访问网络,请按照以下步骤操作:下载并安装SoftEther。您可以从官方网站(https://www.softether.net/)下载最新版本的SoftEther。打开SoftEther客户端,点击左上角的“文件”菜单,选择“连接”。在弹出的“连接”窗口中,选择“虚拟服务器”选项卡。点......
  • CF1442D Sum
    题意给定\(n\)个递增数组。\(k\)次操作,每次你可以选择一个数组,使\(ans\)加上数组的第一个数,并删除。问最大化的\(ans\)的值。Sol考虑当前选择的方案如何变得更优。不难想到,如果当前有两个数组没有选满,则一定可以调整到其中一个变成空的方案,而使得答案不劣。所以,不难......
  • macOS Sonoma 14 beta 5 (23A5312d) ISO、IPSW、PKG 下载
    macOSSonoma14beta5(23A5312d)ISO、IPSW、PKG下载本站下载的macOS软件包,既可以拖拽到Applications(应用程序)下直接安装,也可以制作启动U盘安装,或者在虚拟机中启动安装。另外也支持在Windows和Linux中创建可引导介质。作者主页:sysin.orgmacOSSonoma推出全新功能,全面......