【问题标题】:Physics objects 'drifting' when making continuous contact物理对象在连续接触时“漂移”
【发布时间】:2020-09-21 10:37:00
【问题描述】:

我一直在尝试制作一个简单的 3D 物理引擎作为练习。我的问题是,如果对象的位置没有完全对齐,那么当它们与另一个对象接触时,它们会“漂移”。

在我的测试用例中,顶盒启用了物理功能并受重力影响。底部的盒子是静态的(它的速度固定为 0)。如果顶盒完全位于底盒中心上方(因此它们共享相同的 X 和 Z 坐标),则顶盒会落在底部并完全静止。但是,如果顶盒在 X 或 Z 轴上稍微偏移一点,它会在着陆后开始在该方向上获得动力,直到最终下降,如 here 所示。

我知道是什么原因造成的:当着陆完全居中时,EPA 提供的接触点(我基于this 实施)位于顶盒中心的正下方。这会导致雅可比部分指示要应用于顶盒的约束扭矩 (r1 x normal) 为 0。但是,当偏移时,接触点不在顶盒的正下方盒子的中心不再,导致它稍微旋转。这反过来又会导致下一个时间步的接触法线略微旋转。顶盒沿此接触法线被推出,使其四处滑动。我已经确认了这一点,因为禁用旋转或硬编码以正常接触 0,-1,0 可以解决此问题。

我认为实施联系人缓存可以解决此问题,但正如您在上面的视频中看到的那样,它并没有,其中每个紫色点代表一个活动联系人。我在多个时间步长上缓存联系人,并且每当联系人向对象施加力时,它都会更新与该对象相关的所有联系人(包括其自身)的穿透深度:

private void ApplyForces(RigidBody Body, Time deltaTime, vec3 deltaVel, vec3 deltaRot)
{
    Body.ForcesConstraints += deltaVel;
    Body.TorqueConstraints += deltaRot;

    foreach (Constraint c in M.InvolvedConstraints)
        c.UpdateConstraint(Body, deltaTime, deltaVel, deltaRot);
}

public override void UpdateConstraint(RigidBody Body, Time deltaTime, vec3 deltaVel, vec3 deltaRot)
{
    //I understand that this way of computing the actual positional delta the deltaRot represents is incredibly bad, but I coulnd't get anything else to work (I'm a linear algebra newbie).
    var rot = (quat.FromAxisAngle((deltaTime * deltaRot).Length, deltaRot.NormalizedSafe) * contact) - contact;

    var deltaPos = (deltaVel * deltaTime) + rot;
    
    //The contact normal points from Body1 to Body2
    _pendepth += (Body == Body1 ? 1 : -1) * vec3.Dot(deltaPos, _normal);
}

你可以找到我的代码here(这是choas,对不起)。

【问题讨论】:

    标签: c# game-physics


    【解决方案1】:

    这个问题几乎可以肯定是因为随着时间的推移,非常少的数字会产生影响。滑动非常轻微。我克隆了你的 repo,但我没有运行它的库,所以我能给你的唯一建议就是阅读代码。

    然而,当我看代码时,这样的事情让我非常紧张:

     int index = -1;
     float maxDot = -9999;
     float td = -9999;
     float ti = -1;
    

    我知道你想做什么,但这种事情过去导致我很难找到很多错误。

    至于你的雅可比问题,如果我没记错的话,当雅可比为 0 时,这是一个奇点,你的控制力根本无法移动它。

    您似乎确实认为确实是由表面之间的非常小的角度产生的。我最好的想法是,如果两个表面几乎平行(因此增量角低于某个阈值,那么您可以禁用该物理过程的某些部分。这可能涉及将对象“捕捉”到您描述的稳定状态他们完全一致。

    所以:

    public static float MinAngleThreshold = 0.001f;
            public override void UpdateConstraint(RigidBody Body, Time deltaTime, vec3 deltaVel, vec3 deltaRot)
            {
                //I understand that this way of computing the actual positional delta the deltaRot represents is incredibly bad, but I coulnd't get anything else to work (I'm a linear algebra newbie).
                var rot = (quat.FromAxisAngle((deltaTime * deltaRot).Length, deltaRot.NormalizedSafe) * contact) - contact;
    
                if(rot < MinAngleThreshold)
                {
                    //do something here, potentially just return without updating. 
                }
    
                var deltaPos = (deltaVel * deltaTime) + rot;
                
                //The contact normal points from Body1 to Body2
                _pendepth += (Body == Body1 ? 1 : -1) * vec3.Dot(deltaPos, _normal);
            }
    

    虽然您的代码肯定存在一些问题,但整个系统非常聪明,您应该为自己编写的内容感到自豪。

    【讨论】:

    • 我已经实现了类似于你建议的东西。刚体在多个时间步长上将某个阈值下的约束扭矩存储在一个变量中。该变量在每个时间步都非常显着地减小。如果它或时间步的约束扭矩超过阈值,则将其添加到角速度并设置为零。这使得身体必须在开始应用之前接收一定量的扭矩/秒。这有点拙劣,但它有效。谢谢你的帮助! (也感谢最后的评论。这对我来说真的很重要)
    • 建议并不是真正的答案。这属于 cmets。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    • 1970-01-01
    • 1970-01-01
    • 2014-03-28
    相关资源
    最近更新 更多