一、SOFA是什么
SOFA的主要定位是:用于交互式力学仿真的开源物理仿真框架。
1.SOFA framework
SOFA framework:SOFA框架。
框架不是单个程序,而是一套可以扩展的结构。可以把 SOFA 理解成一个"仿真搭建平台",它提供:基础代码结构、仿真对象的组织方式、各类组件接口、求解器、力学模型、碰撞处理等模块、插件扩展机制、Python 接口、文档与社区支持。
2.SOFA的交互性
如果用一个词概括 SOFA 相比其他商业或开源仿真软件的特点,那就是:interactivity,交互性。
在 SOFA 里,交互性至少有三层意思。
第一层是 用户交互 。
用户可以通过鼠标、键盘、触觉设备等方式操作仿真对象。例如在医学手术仿真里,医生可能用虚拟工具接触器官模型。
第二层是 仿真状态交互 。
程序可以在仿真过程中读取物体状态,比如位置、速度、形变、受力情况,然后根据这些信息继续调整仿真。
第三层是 算法交互 。
SOFA 可以被放进优化循环、深度学习、强化学习等系统中。也就是说,SOFA 不只是"播放一个仿真",而是可以作为一个被算法反复调用、查询、修改的仿真环境。
3.物理引擎
SOFA是一个 physics engine ,物理引擎。
物理引擎可以理解为:用程序模拟物理世界中物体运动、变形、碰撞和相互作用的计算系统。
SOFA 的重点是交互式软体/刚体力学仿真。
二、SOFA的应用方向:从医学仿真到软体机器人
1.SOFA的应用
SOFA 最早的很多应用和医学仿真有关,比如手术仿真、生物力学仿真、医学图像相关的仿真。后来它也被用于动画、计算机图形学、生物学等方向。近些年,SOFA 社区中很大一部分工作开始转向 soft robotics,软体机器人。现在社区里可能有 60% 甚至 70% 的方向与软体机器人相关。
2.soft robotics 软体机器人
软体机器人不是传统金属刚性机器人。它通常由柔软材料构成,比如硅胶、橡胶、柔性结构。它的运动往往来自:气压驱动、腱绳驱动、材料形变、柔性结构变形、与环境的接触和约束。
三、SOFA 的开放核心架构
SOFA 的核心代码是开源的,主要用 C++ 编写,并使用 LGPL 许可协议。它的核心部分属于所有人都可以访问、使用、修改的公共部分。但如果你在核心外面开发新的方法、新的功能、新的模块,这些可以作为插件存在。
1.open core model 开放核心模型
开放核心可以理解为:软件最基础、最公共、最核心的部分开源;围绕这个核心,其他人可以开发自己的扩展功能。
2.LGPL license LGPL 许可协议
开源许可协议决定了你可以如何使用、修改、分发软件。
对于 SOFA 来说,LGPL 大致意味着:你可以下载、使用、修改 SOFA;你可以把 SOFA 集成到自己的项目或产品中;你甚至可以在某些条件下用于商业目的;但如果你修改了 SOFA 核心中大家共用的代码,社区希望你把这些修正或改进贡献回核心。
如果你自己实现了一个新方法,这个新方法不一定属于 SOFA 核心。它可以是你的插件,许可协议由你或你的机构决定。
3.plugin 插件
围绕 SOFA 核心,会有很多独立代码仓库。这些仓库里包含额外功能,通常被称为 plugin,插件。插件在不直接改动 SOFA 核心的情况下,为 SOFA 增加新功能的扩展模块。
四、python bindings Python绑定接口
SOFA 核心是 C++ 写的,但下载 SOFA 时会带有 Python bindings,Python 绑定接口。这个接口可以让用户用 Python 脚本定义仿真,也可以让 SOFA 仿真在普通 Python 环境中运行。
可以理解为:SOFA 本体是 C++ 写的,但它给 Python 开了一个接口,让 Python 可以调用 SOFA 的功能。这就像一个 C++ 引擎加上了 Python 控制台。底层高性能计算由 C++ 完成,上层用户可以用 Python 更方便地创建场景、修改参数、读取数据、控制流程。
五、SOFA 的文档、论坛与学习入口
SOFA 的几个学习入口:SOFA 官网、用户文档、开发者 API 文档、GitHub discussion forum、GitHub 源码仓库。
文档:user documentation 用户文档、API documentation API 文档
GitHub forum:如果有问题,可以去 SOFA 的 GitHub discussion forum 提问。任何有 GitHub 账号的人都可以发问题,社区会在那里回复。
六、SOFA 社区与贡献流程
SOFA 已经发展多年,最早主要在法国科研团队中使用,后来随着欧洲项目和国际合作传播到更多地方。老师提到 SOFA 社区仍然以学术界为主,大约 80% 来自学术背景,剩下部分来自公司、初创企业、中型企业、工业集团和独立开发者。
随着社区扩大,SOFA 需要更正式的组织方式,于是形成了 SOFA Consortium。这个联盟负责维护代码、组织社区、支持协作、管理生态和治理流程。
1.SOFA Consortium SOFA 联盟 / SOFA 协会式组织
SOFA Consortium负责维护 SOFA 项目长期稳定发展的组织结构。
它不是只写代码,还要负责:避免不同团队重复开发却互不知道、促进合作、维护代码、发布稳定版本、修复问题、支持社区、组织技术委员会和执行委员会、让项目做出合理技术决策。
2.SOFA的贡献流程
如果任何人都能直接改 SOFA 核心代码,那很容易破坏已有功能。过去 SOFA 的贡献流程可能比较松,后来社区建立了更严格的贡献流程。
现在更合理的流程是:
- 开发者提出修改;
- 修改放在独立分支或 fork 中;
- 通过 pull request 提交;
- 核心开发者 review;
- 自动化系统检查编译、测试、回归问题;
- 满足要求后合并进 SOFA 核心。
这个过程可以保证代码质量,也能避免某个修改破坏整个社区。
3.pull request:拉取请求 / 合并请求
可以理解为:我修改了一些代码,但不是直接塞进官方项目,而是先提交一个请求,让维护者检查后决定是否合并。
4.continuous integration:持续集成
持续集成就是自动检查代码有没有问题。比如:能不能编译成功、有没有破坏旧功能、测试能不能通过、不同平台是否正常、有没有冲突文件。这些检查可以防止贡献者或核心开发者不小心把 SOFA 搞坏。
七、开始使用 SOFA 的两种方式:Binary 和 Source
1.开始使用 SOFA 有两种方式
第一种是下载 binary version,二进制版本 。
第二种是下载 source version,源码版本,然后自己编译,生成能在自己电脑上运行的可执行程序。
这两个方式决定了你现在是"使用 SOFA",还是"开发 SOFA"。
2.Binary(二进制版本)
Binary(二进制版本)就是官方已经编译好的 SOFA。下载之后可以直接运行,不需要自己处理 C++ 编译、CMake、依赖库、链接错误这些东西。
3.Source code(源码)
Source code(源码)就是 SOFA 的原始 C++ 代码。下载源码以后,不能直接双击运行,而是要经过编译。
八、SOFA 的 GitHub 源码结构
SOFA 的核心代码是开放在 GitHub 上的,很多插件也会放在 GitHub 或 GitLab 上。用户可以通过 Clone 或 Download ZIP 下载源码。
1.Repository代码仓库
Repository(代码仓库)可以理解成:一个项目的代码总文件夹 + 版本历史 + 协作记录
SOFA 的 repository 里不仅有代码,还包括:文件夹结构、编译配置、文档、issue、pull request、分支、贡献记录。
2.Fork/Branch/Pull Request
**Fork(分叉 / 个人副本)**就是你把官方 SOFA 仓库复制一份到自己的 GitHub 账号下。你可以在自己的副本里改代码,不会直接影响官方仓库。
**Branch(分支)**就是为某个修改单独开一条工作线。
**Pull Request(拉取请求 / 合并请求)**就是你改完代码后,请求官方把你的修改合并进去。但官方不会直接合并。核心开发者会 review,也就是检查你的代码是否合理、是否会破坏已有功能。
3.Continuous Integration 持续集成
SOFA 有自动检查流程。这个就是 continuous integration(持续集成)。
它会自动检查:能不能编译、有没有测试失败、有没有破坏旧功能、有没有文件冲突、是否满足合并要求。
九、SOFA Framework 和 SOFA Component 的区别
SOFA 的核心源码不是简单堆在一个文件夹里,而是按照架构组织。最重要的两个部分是:SOFA/framework
SOFA/component
它们的区别是:
SOFA Framework = 架构、骨架、接口规则
SOFA Component = 具体模型、算法、组件实现
1.SOFA Framework(SOFA 框架层)
SOFA Framework(SOFA 框架层)包含 SOFA 的中心结构。它是 SOFA 的 skeleton(骨架)。
它主要定义:一个组件应该如何接入 SOFA、一个 mass 应该有哪些接口、一个 force model 应该如何被调用、一个 solver 应该怎么组织、一个 component 应该有哪些基本行为、SOFA 场景图和对象系统如何工作。
Framework 主要是"规定规则",不是"真正计算某个模型"。
2.SOFA Component(SOFA 组件层)
SOFA Component(SOFA 组件层)才是具体实现所在。
比如:具体的质量模型、具体的弹簧模型、具体的线弹性模型、具体的超弹性材料模型、具体的求解器、具体的碰撞组件。
如果想找 UniformMass、某种 spring model、某个 solver 的具体代码,就应该看 component,而不是 framework。
3.为什么要这样分层
因为 SOFA 是一个可扩展系统。它需要保证所有人写的组件都能以统一方式接入系统。
可以这样理解:Framework = 插座标准 Component = 官方电器 Plugin = 第三方电器
十、Abstract Class:SOFA 为什么需要"抽象类"
framework 里有很多抽象类,它们不真正实现某个具体模型,而是规定一个组件必须实现哪些函数。
1.abstract class(抽象类)
Abstract Class(抽象类)可以理解成一份接口合同。
它不告诉你具体怎么算,但它规定你必须具备哪些能力。例如,如果你说自己是一个质量组件,那么你就必须实现质量组件应该有的函数;如果你说自己是一个求解器,就必须实现求解器应该有的函数。
2.用 Mass 举例
BaseMass 本身不是一个真正能用的质量模型。它只是规定:如果你是一个 mass component,你必须实现哪些函数。
然后具体的质量模型,比如:UniformMass、MeshMatrixMass、DiagonalMass
才是真正的实现。
3.为什么 SOFA 需要抽象类
因为 SOFA 有很多不同组件:mass、force field、solver、collision model、mapping、material model、constraint。它们具体实现可以不同,但必须遵守统一接口。否则 SOFA 不知道怎么调用它们。
抽象类的作用就是:统一接口,不统一具体实现。
十一、Mass / Force Field / Solver:仿真的三类核心组件
1.Mass:质量组件
Mass(质量组件)描述物体的质量属性。
它回答:这个物体有多重、质量如何分布、运动时惯性如何体现。
Mass component 会参与动力学方程。它和 force field、solver 一起决定物体在每个时间步如何运动。
2.Force Field:力场 / 力模型
Force Field(力场 / 力模型)描述物体受到什么力,以及物体变形时产生什么内部力。
常见力包括:重力、弹簧力、阻尼力、接触力、压力、材料内部弹性力、外部施加的力。
Force Field 会告诉 SOFA:当前状态下物体受到哪些力、这些力如何影响下一步位置和速度。
3.Solver:求解器
Solver(求解器)负责求解仿真中的数学方程。
每个时间步,SOFA 都要算:下一时刻物体的位置是什么、速度是什么、受力后如何变形、约束如何满足。这些问题最终会变成数值方程,所以需要 solver。
两类常见 solver:
第一类是 integration scheme / ODE solver(积分方案 / 常微分方程求解器) 。
它决定系统如何从当前时间步走到下一个时间步。
第二类是 linear solver(线性求解器) 。
它负责求解类似:A x = b 这样的线性系统。
Mass 和 Force Field 只是提供物理信息,但真正让仿真推进的是 solver。可以粗略理解为:可以粗略理解为:Mass 决定惯性、Force Field 决定受力、Solver 负责算出下一步状态。
十二、SOFA 的三个核心设计原则
SOFA 有三个很重要、很有特色的设计原则,这三个是:Scene Graph、Animation Loop、Mapping。
十三、Scene Graph:SOFA 如何组织一个仿真
SOFA 用 scene graph(场景图) 来描述一个仿真。
这个 graph 有一个起点,叫 root node,也就是根节点。
根节点下面可以有多个子节点,每个子节点通常对应一个仿真对象。每个对象内部又包含多个 component,用来描述这个对象的属性和行为。
1.Scene Graph(场景图)
Scene Graph(场景图)可以理解为:用树状/图状结构组织仿真对象和组件。
一个简单结构可能是:
root node
│
├── object A
│ ├── MechanicalObject
│ ├── Mass
│ ├── ForceField
│ ├── Solver
│
├── object B
│ ├── MechanicalObject
│ ├── CollisionModel
│ ├── VisualModel
2.Root Node(根节点)
Root Node(根节点)是仿真的入口。
全局参数通常会放在 root node 上,比如:时间步长 dt、重力 gravity、全局动画循环、全局插件加载、场景总体结构。
3.Child Node(子节点)
Child Node(子节点)通常表示一个具体对象。
比如snake、heart、lever、particle。每个对象可以作为一个子节点存在。这个节点内部再放属于它自己的 mass、force field、solver、collision model 等组件。
4.Component(组件)
Component(组件)是 SOFA 场景中真正执行某一类功能的单位。
每个 component 背后通常对应一个 C++ 类。
比如:UniformMass、MechanicalObject、EulerImplicitSolver、CGLinearSolver、TriangleCollisionModel
5.Data
在 SOFA 里,component 的参数通常叫 data。
比如一个 mass component 可能有: totalMass = 1.0 ,这个 totalMass 就是它的 data。
所以看到 SOFA 文档里说 "data",不要理解成普通数据文件,它常常指的是组件的参数或属性。
十四、Animation Loop:每个时间步里发生什么
SOFA 的第二个核心设计是 animation loop(动画循环 / 仿真循环)。
当你点击 SOFA 里的 Animate 或者 Step 时,SOFA 就开始一轮一轮执行仿真。每一轮就是一个 time step(时间步)。
1.Animation Loop
Animation Loop 负责定义:每一个时间步里,SOFA 应该按什么顺序做哪些事。
Animation Loop 自己并不真正计算力、碰撞或位置。它只是发出命令,告诉其他组件该做什么。
Animation Loop 像一个"blind chief orchestra",也就是盲人乐队指挥。它会发出命令:现在进行碰撞检测、现在求解力学系统、现在进入下一步,但它自己并不知道场景里是不是真的有碰撞组件,也不知道是不是真的有求解器。它只是按流程发命令。
2.DefaultAnimationLoop
如果你没有显式添加 animation loop,SOFA 可能会默认创建一个 DefaultAnimationLoop。它通常会做两类事情:碰撞检测、力学求解。
不要完全依赖默认行为,最好明确写出你使用哪个 animation loop。这样你知道自己的仿真到底按什么流程执行。
十五、Mapping:为什么一个物体可以有多套网格
SOFA 的第三个核心设计是 mapping(映射)。
在 SOFA 中,一个对象可以使用不同的表示方式:用于力学计算的网格、用于碰撞检测的网格、用于渲染显示的网格。这三套网格可以相同,也可以不同。
1.为什么需要多套网格
因为不同任务对网格的要求不同。
力学计算
力学计算需要准确表达物体受力和变形,但网格太细会导致计算很慢。
碰撞检测
碰撞检测可能需要更适合判断接触的表面网格。
渲染显示
渲染希望物体看起来光滑、漂亮,所以可能用更细更平滑的表面网格。
2.Mapping(映射)
Mapping(映射)就是把一套表示的运动传递到另一套表示上。
比如:力学网格移动了→ mapping 把这个运动传给渲染网格→ 渲染网格跟着变形
或者:力学网格计算真实变形→碰撞网格根据力学网格更新位置
Mapping 解决的是:不同网格之间如何保持一致
3.Mapping 为什么很重要
这涉及一个很核心的工程权衡:精度 vs 速度
可以用粗网格做力学计算,提高速度;
用细网格做渲染,提高视觉效果;
用合适的表面网格做碰撞检测,提高碰撞效率。
Mapping 让这些不同表示能协同工作。
十六、有限元、网格与拓扑
物理引擎概念中一个重点是 finite element method(有限元方法,FEM) 。
SOFA 大量使用有限元方法。有限元方法需要对空间进行离散化,因此需要 mesh(网格)或 point cloud(点云)。在 SOFA 中,mesh 很常见。
1.Finite Element Method 有限元方法
Finite Element Method(有限元方法)可以粗略理解为:把一个复杂连续物体切成很多小单元,然后在这些小单元上近似求解力学方程。
真实物体是连续的,比如一块软材料、一个器官、一个软体机器人。但计算机不可能对无限连续的点全部精确计算,所以要离散化。
2.Mesh(网格)
Mesh(网格)就是空间离散化的结果。
比如:3D 体网格:四面体、六面体;2D 表面网格:三角形、四边形;1D 线网格:线段、梁单元。
如果要计算一个软体物体的变形,就需要知道它被划分成哪些单元,这些单元如何连接。
3.Topology(拓扑)
Topology(拓扑)在这里不是抽象数学里的高深概念,可以先理解为:网格中点、边、面、单元之间的连接关系。
比如:哪些三角形连接在一起、一个顶点周围有哪些三角形、哪些四面体共享同一条边、网格内部如何连通。
几何告诉你点在哪里;拓扑告诉你它们怎么连。
4.Shell Model 和 Beam Model
有时不用完整 3D 网格,如果一个物体特别薄,比如布料、衣服,可以用 shell model(壳模型)。它忽略厚度方向的复杂离散,但仍然把厚度影响纳入力学计算。
如果一个物体像线一样细长,比如导丝、内窥镜、细杆,可以用 beam model(梁模型) 。
它主要沿一条线建模,但可以在力学模型里考虑截面、刚度、惯性等信息。
5.什么时候用 3D Mesh
如果物体在三个方向上都不能忽略,比如心脏、器官、复杂软体结构,就需要 3D 网格。
判断标准:
如果某个维度很薄,可以考虑 shell。
如果两个维度都很小,只剩长度主导,可以考虑 beam。
如果三个维度都重要,就用 3D mesh。
十七、Collision Pipeline:SOFA 如何处理碰撞
SOFA 的 **collision pipeline(碰撞流水线)**用于判断物体是否碰撞,以及发生碰撞后如何响应。
碰撞处理通常分成几个步骤。
1.Broad Phase 粗检测
第一步是 broad phase(宽阶段 / 粗检测)。
它不会一上来检查所有三角形、所有点、所有表面,那样太慢。它先用 bounding box(包围盒)快速判断两个物体有没有可能碰撞。
如果两个包围盒都不相交,那物体肯定没碰撞,可以直接跳过。
2.Narrow Phase 细检测
如果包围盒相交,就进入 narrow phase(窄阶段 / 精细检测)。
这一步会更细致地检查:三角形和球是否相交、点和面是否接触、具体哪些 collision primitives 发生了接触。
3.Collision Primitive 碰撞基元
Collision Primitive(碰撞基元)就是用于碰撞检测的基本几何单位。
比如:sphere、triangle、line、point、capsule、box。
碰撞检测不是对"整个复杂物体"直接算,而是对这些基本几何单元进行检查。
4.Collision Response 碰撞响应
检测到碰撞后,还要决定怎么处理,这叫 collision response(碰撞响应)。
有两类主要方法:
Penalty Method 惩罚法
惩罚法的思想是:如果两个物体发生穿透,就根据穿透深度施加一个修正力。可以把它想象成弹簧:穿透越深,反弹力越大。
优点是简单,缺点是参数可能不好调,可能不稳定。
Constraint-based Method 约束法
约束法更复杂。它不是简单加一个弹簧力,而是求解一个约束问题,让物体满足"不穿透"的条件。优点是更稳定,缺点是计算量更大。