物理引擎如何建模 angular 速度和 angular 加速度(3D)

How do physics engines model angular velocity and angular acceleration (in 3D)

我一直在寻找这个问题的答案,但找不到直接的答案。 好像网上的说法是 angular velocity 存储为一个向量,方向是旋转的轴,长度是旋转的速率。 这有一定道理,但它完全忽略了您实际使用它的方式。 所以有2个关键操作需要完成。

  1. 应用 angular 加速度(如何将 angular 加速度(大概在相同的数据类型中)添加到您的 angular 速度?)
  2. 计算刚体上一点的速度,同时考虑angular和线速度。

我研究了像 bullet 这样的物理引擎是如何处理它的,但是数学非常混乱,一些运算符重载似乎隐藏了实际发生的事情。

首选 C++ 或 C 语言的答案,但只要数学清楚,一切都可行。

我建议阅读论文 基于物理的建模简介:刚性 Body 模拟 I - 无约束刚性 Body 动力学 作者 David Baraff。

在论文中,您有刚性 body 的 angular 动量 L 和 angular 速度 ω 的模型。但它不使用 angular 加速度,因为 angular 动量取决于施加于它的扭矩。您还需要定义自己的惯性张量 I(t),它“描述 body 中的质量如何相对于 body 的质心分布。”在论文中还有一个用 C 语言实现的整个模型(质量、力、扭矩、angular 动量)。

这个 repo 也有一个论文的实现(在 C 中):RiBOEngine

来源:https://www.cs.cmu.edu/~baraff/sigcourse/notesd1.pdf

在bullet3物理引擎中,实现与上面描述的论文非常相似。如果您查看 btRigidBody::integrateVelocities() method,您会发现为了计算 angular 速度,您需要张量和扭矩来计算它。

数据结构

首先是一些数据结构。

线性代数

我假设有 Vector3 类 用于 3 个值的向量和 Quaternion 类 用于 4 个值的旋转以及 Matrix3 用于 3×3惯性和旋转等操作。每个都遵循其相应的线性代数规则(加法 +、减法 -、缩放 * 和乘积 *)。每个 object 都有自己的规则来处理这些运算符。根据上下文,它可能是比例运算,或 matrix-vector 乘积,或四元数乘积。

Body 属性

每个body都有一个标量质量m,和一个body质量惯性张量I_body 用于运动方程。您还应该保持逆惯性 I_body_inv = inverse( I_body ).

Body 州

body 的当前状态,包含其位置 r (Vector3),方向 q (Quaternion) 和运动。但通常不是存储两个速度矢量(平移vVector3),旋转ωVector 3)) , 存储两个动量向量(线性p(Vector 3),angularL(Vector 3 )).每个都在质心处求和。

状态向量有 13 个分量,因为每个向量有 3 个分量,而四元数 q 有 4

Y = [r, q, p, L ]

您可以选择使 Y 成为 array/vector 或 struct 并使用适当的运算符来执行线性代数。

运动提取

给定 Y 你总是可以提取平移速度 v 和 angular 速度 ω[= body 的 134=] 具有以下算法

[v, ω] = GetMotion(Y)
// Get Motion from Y=[r, q, p, L]

r = Y[0:2];
q = Y[3:6];
p = Y[7:9];
L = Y[10:12];

// Get 3×3 rotation matrix R from orientation q
R = rotation(q);

// Get 3×3 inverse mass moment of inertia on the world frame
I_inv = R * I_body_inv * transpose(R);

// Get translational velocity vector
v = p / m;

// Get rotational velocity vector
ω = I_inv * L;

return [v, ω];

还需要反向设置body

的初始条件
Y = GetState(r, q, v, ω)
// Get state vector from position & motion

// Get 3×3 rotation matrix R from orientation q
R = rotation(q);

// Get 3×3 mass moment of inertia on the world frame
I = R * I_body * transpose(R);

// Get translational momentum vector
p = m * v;

// Get angular momentum vector
L = I * ω;

return [r, q, p, L];

整合

在每个时间步,施加的负载向量(力 F、扭矩 τ)都会改变 body 状态向量。

Body 状态率

body状态的时间率,我称Y',计算如下

Y' = GetRate(t, Y)
// Assemble the time rate of the body state Y at time t

r = Y[0:2];
q = Y[3:6];
p = Y[7:9];
L = Y[10:12];

// Get the motion of the body
[v, ω] = GetMotion(Y);

// Get the loading
[F, τ] = GetLoading(t, r, q, v, ω);

r' = v
q' = 0.5*quaterion(ω)*q
p' = F
L' = τ

return [r', q', p', L'];

其中 quaternion(ω) = [ωx,ωy,ωz,0] 和四元数乘积 * 符合四元数规则。

Runge-Kutta 4

使用 RK4 方法随时间积分很常见

ΔY = GetStep(t, Y, h)
// Integrate state vector Y, over time step h at time t

K0 = GetRate(t, Y);
K1 = GetRate(t+h/2, Y + h/2*K0);
K2 = GetRate(t+h/2, Y + h/2*K1);
K3 = GetRate(t+h, Y + h*K2);

return h/6*(K0 + 2*K1 + 2*K2 + K3);

模拟

模拟是状态向量在许多有限时间步长上的积分。

Body 加载中

需要一个加载函数来描述每个 body 在每个时间帧上的 forces/torques 来评估状态率

[F,τ] = GetLoading(t, r, q, v, ω)
// Describe the force vector F, and torque vector τ
// from time t, position r, orientation q,
// velocity v, and rot. velocity ω

模拟循环

并且模拟循环积分直到达到目标时间

Y = GetState(r, q, v, ω);
time = 0;
while(time<target_time)
{
    Y += GetStep(time, Y, h);
    time += h;
}

这就是单刚性 body 模拟的基础知识。

Post 处理中

当您拥有 body 状态 Y 时,您将获得 [v, ω] = GetMotion(Y) 的质心运动。但是要回答第二个问题,您需要任意点的速度 v_A。该点位于矢量 r_A

v_A = GetVelocityAtPoint(Y, r_A)
// Get velocity of world point A
r = Y[0:2]    
[v, ω] = GetMotion(Y)

return v + cross(ω, r_A - r);

如果你想让一个点v_B的速度骑在body上,并由一个局部向量定义r_B_body 然后

v_B = GetVelocityAtBodyPoint(Y, r_B_body)
// Get velocity of body point B
r = Y[0:2]    
q = Y[3:6]
R = rotation(q)
r_A = r + R*r_B_body
return GetVelocityAtPoint(Y, r_A)

请注意,在许多步骤中,方向 Quaternion q 可能会偏离单位四元数,因此需要 re-normlized.

q = normalize(q)
// m = sqrt(qx^2+qy^2+qz^2+qw^2)
// q = q/m

根据我的经验,偏离单位四元数的情况不会逐渐恶化,因为它往往会先偏离然后再回到单位四元数,由此产生的方向角误差实际上非常小。