1.AnimationLoop
回顾 SOFA 一个完整仿真需要哪些东西,首先需要 AnimationLoop。
它负责:决定一个时间步里面,各个计算步骤按照什么顺序执行,也就是:
当前时间 t
↓
AnimationLoop组织各项计算
↓
完成一个仿真时间步
↓
进入 t + dt
即使不写,也存在默认 AnimationLoop
<Node name="root" dt="0.01" gravity="0 0 0"> <DefaultAnimationLoop /> <DefaultVisualManagerLoop /> <Node name="myObject"> ... </Node> </Node>默认情况下 SOFA 会提供一个 DefaultAnimationLoop。但仍然建议最好在场景里面明确写出来你要用哪个 AnimationLoop。
主要有两类:DefaultAnimationLoop 和 FreeMotionAnimationLoop
2.DefaultAnimationLoop 和 FreeMotionAnimationLoop
2.1.DefaultAnimationLoop
DefaultAnimationLoop
Collision detection(碰撞检测)
(主要对应 penalty response)
↓
Physics resolution(求解物理运动)
↓
AnimateVisitor + Update(更新状态)
2.2.FreeMotionAnimationLoop
先计算 free physics
↓
碰撞检测
↓
求解约束
↓
综合物理 + 约束/碰撞
↓
修正运动
FreeMotionAnimationLoop 的 Scene Graph 通常要更复杂,原因是它还需要 Constraint Solver 去真正求解约束。在碰撞问题里,它可以进一步求 接触力(contact forces)
- DefaultAnimationLoop
→ 普通物理仿真流程 - FreeMotionAnimationLoop
→ 自由运动 + 约束/接触求解
3.MechanicalObject 的 template
一定要注意 MechanicalObject 的 template。
因为 template 决定这个 MechanicalObject 中的自由度是什么类型
例如:
<MechanicalObject template="Rigid3d" name="myParticle" position="0 0 0 0 0 0 1" />这里 Rigid3d 意味着它描述的是一个三维刚体自由度。所以position不只是三个坐标,还包括刚体姿态。
4.单个刚体不会形变
接着把问题缩小到 Single rigid body(单个刚体)。这里暂时不考虑铰接系统、多刚体、复杂角运动,只考虑一个刚体。
牛顿方程可以写:
其中:
:外力
:内力
但对于的这个理想单刚体,不讨论使物体内部发生形变的内部自由度。
所以重点是:外力刚体整体移动,但刚体不形变,
即 Rigid = no internal force = no deformation
可以用刚体的质心来描述它的整体平移运动。于是可以写:
其中是质心的位置。
4.1.牛顿记号(点记号)
是牛顿记号(点记号),专门用来对时间 t 求导:
| x = x(t) | 位置(关于时间的函数) |
| ẋ = dx/dt | 一阶导:速度 v |
| ẍ = d²x/dt² | 二阶导:加速度 a |
所以等式直接等价: ∑F = mẍ ⇔ ∑F = ma
5.积分方案(integration scheme)
<MechanicalObject template="Rigid3d" name="myParticle" position="0 0 0 0 0 0 1" /> <UniformMass totalMass="1"/> <ConstantForceField totalForce="1 0 0 0 0 0" />我们已经有:MechanicalObject + Mass + Force
从物理方程来看:似乎已经足够,但当时点 Animate,物体还是不会动。
因为当时缺了Integration scheme + Linear solver
其中最先缺的是:积分方案(integration scheme),因为还需要告诉计算机怎么从当前时间的状态算到
。
Integration scheme 的作用就是完成:
这就是我们为什么一直需要 EulerImplicitSolver 之类的 ODE Solver,它告诉我们怎样从当前仿真状态进入下一状态。
6.积分方案分显式和隐式
计算这个力的时候,应该使用哪个时间点的
?
6.1.Explicit——显式积分
显式方法使用已经知道的状态。例如,
或者它们的某种组合。
只要使用 过去状态 或 当前状态 这些已经知道的值,就是显式方法。
Explicit:用已知状态计算力
优点:简单、计算效率高
缺点:通常要求很小的
比如,那么为了模拟
,就需要
个时间步。
所以单步虽然简单,但如果时间步必须特别小,整个仿真仍然可能很慢。
6.2.Implicit——隐式积分
另一种策略是,也就是:用下一时间步的位置计算力。
问题在于:现在还不知道。因此求解就变得更复杂。
Implicit:力依赖尚未求出的未来状态
优点:可以使用更大的时间步。
缺点:每一步要求解的数学系统更复杂。
| 方法 | 优点 | 缺点 |
|---|---|---|
| Explicit | simple, efficient | requires a small |
| Implicit | allows larger | more complex system to solve |
7.积分方案最终会产生线性系统
不论使用显式还是隐式,问题最终归结成:
注意这里的 x 不是前面 MechanicalObject 的位置 x 本身,这里是线性代数里:待求未知量。
这里用显式 Euler 举例。
,显式方法是在当前状态
计算力。
7.1. 加速度写成速度变化
定义:
于是:
代回:
得到:
两边乘:
SOFA 中这里求的通常不是 acceleration,而是 velocity increment。
即不是直接把作为未知量,而是求
于是刚才的线性系统对应这个简单显式例子:
,
,
8.区分 Integration Scheme 和 Linear Solver
Integration Scheme
例如:EulerExplicitSolver、EulerImplicitSolver
负责:把动力学方程整理成线性系统。
也就是决定是什么,
是什么,以及未知量是什么。但是它不负责真正把
解出来。
Linear Solver
真正负责求解的是 Linear Solver ,比如前面使用的 CGLinearSolver 。
ODE / Integration Solver
↓
建立 Ax = b
↓
Linear Solver
↓
真正求 x
9.Linear Solver 分两类
线性求解器大致有两类。
9.1.Direct Solver——直接求解器
思想接近:,也就是直接求精确解。可以理解为试图对矩阵
做直接处理。
特点:精度高 / 求解直接;但可能计算成本高
9.2.Iterative Solver——迭代求解器
比如:CG = Conjugate Gradient = 共轭梯度法
它不是一次就得到答案,而是:先猜一个解算误差
修正
再算误差
继续修正...
残差:,其中
是当前第
次迭代的解。
目标就是:,也就是不断减小
的误差。
所以 CGLinearSolver 本质上就是通过迭代不断逼近 Ax=b 的解。
10.MeshLoader是被动组件
MeshLoader 是 passive component(被动组件)。它自己不会计算物理,它做的只是:
读取 mesh 文件
↓
读取点、边、三角形、四面体等信息
↓
把这些信息提供给场景
通常再连接 TopologyContainer 或其他 Mesh Topology 组件。
因此:MeshLoader ≠ 物理模型,它只是数据加载器。
11.仿真模型需要哪些东西
总结物理部分,前面加入过 Mass(UniformMass、MeshMatrixMass),External Force(ConstantForceField),Mechanical Constitutive Law(例如之前肝脏里的线弹性模型)
也就是:
MechanicalObject
+
Mass
+
Force / ForceField
+
Constitutive Model
构成基本机械模型。
假设已经有 MechanicalObject、Mass、Force 甚至已经有材料模型,但没有 Integration Scheme + Linear Solver,物体还是不会真正从推进到
Physics ≠ 完整可运行仿真,还需要数值求解部分:EulerImplicitSolver + CGLinearSolver
12.Mapping 把 representation 连起来
当从刚体例子进入之前的 Deformable Liver,场景开始增加其他 representation:Mechanical Model、Rendering Model、Collision Model
如果三种 representation 使用不同 Mesh:Mechanical Mesh、Visual Mesh、Collision Mesh,就需要 Mapping 连接它们。