在3D固定关节约束中计算两个物体的脉冲/扭矩

时间:2019-04-03 09:24:04

标签: algorithm constraints game-physics physics-engine

我有2个刚体(ab)和1个固定关节约束(具有相对变换rela)。

我的目标是实现:-

否。 1。 b.transform = a.transform * rela
否。 2。重心(a + b)不变。
否。 3。 (第三牛顿法则)整个系统的速度(a + b)不变。
否。 4。 (第三牛顿法则)整个系统的角速度(a + b)不变。
否。 5。应该最小化两个对象的移动/旋转以解决该问题。

我希望对两个物体施加冲击/扭矩,以使其逐渐满足要求。
该视频可以描述我想要的-(youtube link)。

如何解决适用于每个身体的冲动/扭矩值?
我想要一个大概的主意/算法。
它可以是没有任何代码的描述文本。

示例

这是一个示例问题及其正确的解决方案(即最终的静止状态):-

enter image description here

代码(草稿)

这是我当前的代码段,以防万一:-

class Transform {
    Vec3 pos;
    Matrix33 basis;
};

每个刚体都具有以下字段:-

class RigidBody {
    float mass;
    Matrix33 inertiaTensor;
    Transform transform;
    Vec3 velocity;
    Vec3 angularVelocity;
};

固定关节约束为:-

class FixConstraint {
    Transform rela;
    RigidBody* a;
    RigidBody* b;
};

我糟糕的解决方案草案

简而言之,我必须修改12个变量。

  • ab(xyz-6个变量)的位置
  • a和b的方向(角度xyz-6个变量)

然后我可以使用“我的目标” 1和2来创建一些方程式。
然后,充其量来说,我必须用12个未知变量求解12个线性方程。
我怀疑是否必须那么难。

我以前的互联网搜索

我研究了各种来源,但主要是:-

  • 只需进入迭代求解器即可。
  • 尝试对角化矩阵+ jacobian:只有专家才能理解。
  • “您为什么不查看(在此处插入物理引擎的名称)源代码?”没有对初学者友好的解释。
  • 显示如何使用(物理引擎名称)创建固定关节约束。

以下是一些最有用的方法:-

(编辑了一些措辞和规则,感谢 fafl Nico Schertler 。)


(几天后编辑添加)
我相信,如果可以破解“ <子弹物理学”的“ Point2PointConstraint.cpp”,我将完全理解该算法和原理。

为了以防万一,我也在这里复制粘贴了它。 这是第一部分:-

SimdVector3 normal(0,0,0);
for (int i=0;i<3;i++)
{
    normal[i] = 1;
    new (&m_jac[i]) JacobianEntry(
        m_rbA.getCenterOfMassTransform().getBasis().transpose(),
        m_rbB.getCenterOfMassTransform().getBasis().transpose(),
        m_rbA.getCenterOfMassTransform()*m_pivotInA - m_rbA.getCenterOfMassPosition(),
        m_rbB.getCenterOfMassTransform()*m_pivotInB - m_rbB.getCenterOfMassPosition(),
        normal,
        m_rbA.getInvInertiaDiagLocal(),
        m_rbA.getInvMass(),
        m_rbB.getInvInertiaDiagLocal(),
        m_rbB.getInvMass());
    normal[i] = 0;
}

这是第二部分:-

SimdVector3 pivotAInW = m_rbA.getCenterOfMassTransform()*m_pivotInA;
SimdVector3 pivotBInW = m_rbB.getCenterOfMassTransform()*m_pivotInB;
SimdVector3 normal(0,0,0);
for (int i=0;i<3;i++)
{       
    normal[i] = 1;
    SimdScalar jacDiagABInv = 1.f / m_jac[i].getDiagonal();

    SimdVector3 rel_pos1 = pivotAInW - m_rbA.getCenterOfMassPosition(); 
    SimdVector3 rel_pos2 = pivotBInW - m_rbB.getCenterOfMassPosition();
    //this jacobian entry could be re-used for all iterations

    SimdVector3 vel1 = m_rbA.getVelocityInLocalPoint(rel_pos1);
    SimdVector3 vel2 = m_rbB.getVelocityInLocalPoint(rel_pos2);
    SimdVector3 vel = vel1 - vel2;

    SimdScalar rel_vel;
    rel_vel = normal.dot(vel);

    //positional error (zeroth order error)
    SimdScalar depth = -(pivotAInW - pivotBInW).dot(normal); //this is the error projected on the normal

    SimdScalar impulse = depth*m_setting.m_tau/timeStep  * jacDiagABInv -  m_setting.m_damping * rel_vel * jacDiagABInv;

    SimdVector3 impulse_vector = normal * impulse;
    m_rbA.applyImpulse(impulse_vector, pivotAInW - m_rbA.getCenterOfMassPosition());
    m_rbB.applyImpulse(-impulse_vector, pivotBInW - m_rbB.getCenterOfMassPosition());

    normal[i] = 0;
}

1 个答案:

答案 0 :(得分:2)

顾名思义,约束是对两个物体运动的限制。因此,要成功地对约束进行建模,必须完全了解该约束施加在两个物体上的约束,并将这些约束表示为冲量或力方程。基于脉冲的求解器几乎总是在基于力的求解器上使用,因为一阶(速度)方程(以及所涉及的数字)比二阶(加速度)方程更容易计算和处理。因此,我们将研究对一般一维约束(n-D约束可以表示为一个或多个一维约束)的冲动建模。

建模一维约束的一阶(速度)约束

使用脉冲对约束建模的第一步是将约束表示为一阶(速度/动量)方程。有问题的固定约束(此后简称为“约束”)具有简单的位置(零阶)约束:约束空间中的线性和角位置必须保持相同(由相对变换rela指定) )。通过考虑以下事实,我们可以将其转换为一阶限制:为了使两个物体的相对位置恒定,它们的相对速度必须为零。因此,约束可以解释为确保约束空间中两个物体的相对线速度和角速度为零。

现在可以将约束建模为必须应用于系统的脉冲,以便相对速度变为零。如果v1v2是系统的初始和最终相对速度,则m是系统的质量,而j是为满足约束而施加的脉冲,则

v2 = v1 + j/m    (1)
v2 = 0           (2)

第一个方程对任何约束都成立(这是牛顿第二定律),而第二个方程(此后称为约束方程)仅对给定约束成立。

建模一维约束的零阶(位置)约束

我们的模型现在确保相对位置保持恒定(一阶限制),但是我们仍然没有强制执行零阶限制,即B相对于A的相对位置必须是由相对变换rela。如果x1是约束系统位置中的“错误”,则可以使用Baumgarte项beta * x1/h将其“注入”到脉冲中,其中h是时间步长,{{1 }}是一个通常介于0到1之间的参数。您可以考虑将beta设为您希望求解器解析每一帧的位置误差的一部分。

我们的约束方程现在变为

beta

注意:如果求解器使用半隐式积分,则v2 + beta*x1/h = 0 是初始位置误差,但是如果求解器使用隐式积分,则误差实际上是x1

阻尼/软化约束

正如您所指出的那样,您要查找的约束是软约束(或阻尼约束),因为您希望运动逐渐发生。为了指数衰减该约束,我们模拟了指数衰减微分方程,并添加了一个与x2 = x1 + v2*h成比例的值(随着时间步长变得无限小,该值与速度j的导数成比例)。

我们的约束方程现在变为

dv = v2 - v1

v2 + beta * x1/h + gamma * j = 0 是另一个参数(也称为阻尼系数)。如果为gamma >= 0,则约束将为无阻尼或刚性。

从两个方程式中消除gamma = 0并求解v2的结果方程式

j

求解器将此脉冲及其负数应用于约束的轴(通常称为 normal )的方向。

适应和使用一般的一维软约束

可以将这种通用的一维软约束脉冲重新用于创建许多不同的接头,例如固定接头,弹簧接头等。对于固定关节,我们希望约束将B在A空间中的位置和旋转限制为相对变换的位置和旋转。因此,在A的空间中,j = -(v1 + beta * x1/h) * (1/(gamma + 1/m)) | equivalent mass | x1 = B.position - rela.origin。但是,由于我们要限制3维的位置(即v1 = relative velocity of A and B along x1是3d向量,而不是标量),因此我们可以分别求解三个轴上每个轴的一维约束。然后可以使用相同的约束对角位置和速度重复此过程。对于任何一维约束,可以遵循相同的过程来得出冲量。

应用约束

一旦计算,脉冲x1必须以相反方向应用于两个物体,以使约束系统上的总脉冲为零。当为正时,这种冲动意味着将两个物体推开,而当为负时,则意味着将它们拉在一起。因此,如果约束法线点从主体A到主体B(按照约定),则j应用于主体A,-j * normal应用于主体B。

如果需要,可以将一维约束扩展到n维。诸如弹簧/距离约束之类的一些约束沿着一条线,因此在+j * normal维空间中本质上是一维约束,不需要针对不同轴进行多次求解迭代。

如果您的求解器使用热启动,则还可以修改约束方程式以考虑累积的冲量,同时进行阻尼以保持稳定性。阻尼项变为n,其中gamma * (j + j0)是在热启动过程中施加的累积脉冲。

与Bullet 1.5约束求解器的区别

必须指出的是,您要查找的固定约束实际上是Generic6DofConstraint(所有6个自由度都受限制),而不是j0(类似球形和承窝形)联合)在项目符号1.5中。您可以查看其实现以供参考。 Bullet 1.5约束求解器使用略有不同(且稍为波浪形)的阻尼脉冲方程式(在该方程中,他们将相对速度与阻尼因子相乘,但未将其与等效质量的倒数相加),从而使约束有所阻尼更多。这取决于您要选择的偏好,但是我认为这里使用的那个更合理,也更容易适应其他自然的软约束(例如,根据弹簧频率和阻尼比对约束进行参数化以模拟一个阻尼弹簧)。

您还可以在我的2D物理引擎中查看有希望的自解释的阻尼弹簧代码(软距离约束)solver