本文译自作者的韩语原文。 阅读原文(한국어)
刚体动力学中的有限元法:基于四元数的旋转处理与惯性张量
线性四面体的12个节点自由度经刚体约束收成6个。时间步须小于2/ω_max,中间姿态靠SLERP才留在球面上。
刚体动力学的基本方程
在 FSI 或 DEM 里,刚体只要守住任意两点的距离。没有变形。数值上先垮掉的,通常是旋转的写法。
粒子 的速度是质心平动加上转动。
是质心速度, 是角速度, 是从质心到粒子的位移。
轴滑块和 ω 一动,角速度大小可以不变,转轴和转完一圈的时间会变。
주황 화살표가 회전축. 사원수 q를 직접 곱해 매 프레임 큐브를 회전시킨다 — 짐벌락 없이 안정.
把 ω 调大,姿态转得更快。把轴扳到体对角,看角度会不会锁死在某一根坐标轴上。
线动量与角动量
根据牛顿第二定律,总线动量为:
其中 是总质量。质心的加速度为:
总角动量为:
其中 是惯性张量 (inertia tensor), 是总力矩 (torque)。
惯性张量
相对于质心的惯性张量定义为:
扩展到连续体:
其中 是单位张量, 是外积 (outer product)。
当物体旋转时,惯性张量会通过方位矩阵 (orientation matrix) 进行变换。
其中 是在物体坐标系 (body frame) 下的初始惯性张量,只需在模拟开始时计算一次。
使用四元数表示旋转
使用欧拉角 (Euler angles) 表示旋转会产生万向节死锁 (gimbal lock) 问题。为了避免这种情况,我们使用四元数 (quaternion)。
四元数 由标量部分和矢量部分组成。
绕单位轴 旋转角度 的四元数为:
两个四元数 和 的复合旋转可以用它们的乘积表示。
利用四元数,方位矩阵 可以显式地用四元数分量表示。
四元数的时间导数与角速度 相关联。
的对角是 ,非对角是 。质量只在 轴上时 ,于是 ,而 。绕这根轴没有力臂。 就是这个和,在物体系里只存一次。世界系里只要重做 。拿物体系张量直接去乘世界系角速度,力矩的轴就偏了。换坐标时的合同变换,在 本构张量的坐标变换 里,单元刚度也是同一个样子。
时 、,所以 。这就是代码放进去的初值。 时 、,标量消失,矢量部分只剩轴 。 时 ,于是 。旋转和单位旋转相同,只是符号相反。四元数把一整圈盖了两遍。 的 元是 ,在这个单位值上等于 1,非对角为 0。 前面的 和转角公式里的 是同一个因子。照原样累加, 就会偏离 1。线性四面体 4 个节点,每点 3 个位移,共 12 个自由度。刚体约束收成平动 3 加转动 3,一共 6 个。 只是坐标还闭合,它自己变不出这 6 个。
有限元离散化与四面体形函数
在有限元法 (FEM) 中离散刚体的位移场时,四面体单元的形函数 (shape function) 为:
其中 是无量纲体心坐标 (barycentric coordinates),满足 。
在线性四面体单元中,位移插值为:
应用刚体约束条件后,自由度减少为 6 个(3 个平移 + 3 个旋转)。
Python 实现示例#
import numpy as np
class RigidBody:
def __init__(self, mass, I_body):
self.M = mass
self.I0 = I_body # 物体坐标系下的惯性张量 (3x3)
self.x_cm = np.zeros(3) # 质心位置
self.v_cm = np.zeros(3) # 质心速度
self.q = np.array([1., 0., 0., 0.]) # 四元数 (w, x, y, z)
self.omega = np.zeros(3) # 角速度
def quaternion_to_rotation(self):
w, x, y, z = self.q
return np.array([
[1-2*(y**2+z**2), 2*(x*y - w*z), 2*(x*z + w*y)],
[2*(x*y + w*z), 1-2*(x**2+z**2), 2*(y*z - w*x)],
[2*(x*z - w*y), 2*(y*z + w*x), 1-2*(x**2+y**2)]
])
def inertia_world(self):
R = self.quaternion_to_rotation()
return R @ self.I0 @ R.T
def step(self, F_ext, tau_ext, dt):
# 线运动积分
a_cm = F_ext / self.M
self.v_cm += a_cm * dt
self.x_cm += self.v_cm * dt
# 旋转运动积分
I_world = self.inertia_world()
alpha = np.linalg.solve(I_world, tau_ext - np.cross(self.omega, I_world @ self.omega))
self.omega += alpha * dt
# 四元数更新
omega_q = np.concatenate([[0.], self.omega])
q_dot = 0.5 * quaternion_multiply(omega_q, self.q)
self.q += q_dot * dt
self.q /= np.linalg.norm(self.q) # 单位四元数归一化
def quaternion_multiply(q1, q2):
w1, x1, y1, z1 = q1
w2, x2, y2, z2 = q2
return np.array([
w1*w2 - x1*x2 - y1*y2 - z1*z2,
w1*x2 + x1*w2 + y1*z2 - z1*y2,
w1*y2 - x1*z2 + y1*w2 + z1*x2,
w1*z2 + x1*y2 - y1*x2 + z1*w2
])省掉归一化,R就不再是旋转#
每一步不把 拉回 1, 的列就失去单位长度。代码里的 q /= norm(q) 就是这次除法。力矩在世界系里算完,又直接去乘 , 会指向另一根轴。世界系张量要先写成 ,才和 对齐。显式步长的上限是 。 一大,步长先缩短。
两个姿态之间拉直线,路径会沉进单位球里面。 落到 、 落到 的才是 SLERP。
线性插值守不住这个球面。把转动自由度和单元变形混在一起数,约束会吃掉运动,和 薄壳的剪切锁定 里板越薄越僵是同一类账。刚体的自由度是 6,不是 12。
相关文章
如果对您有帮助,请分享。