SOFA的介绍

一、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 的贡献流程可能比较松,后来社区建立了更严格的贡献流程。

现在更合理的流程是:

  1. 开发者提出修改;
  2. 修改放在独立分支或 fork 中;
  3. 通过 pull request 提交;
  4. 核心开发者 review;
  5. 自动化系统检查编译、测试、回归问题;
  6. 满足要求后合并进 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 上。用户可以通过 CloneDownload 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 约束法

约束法更复杂。它不是简单加一个弹簧力,而是求解一个约束问题,让物体满足"不穿透"的条件。优点是更稳定,缺点是计算量更大。

相关推荐
老马啸西风1 年前
SOFABoot-05-依赖管理
算法·spring·微服务·云原生·中间件·springboot·sofa
bug菌¹3 年前
Spring Boot进阶(91):从零开始,轻松打造Sofa+Spring Boot分布式开发环境
java·spring boot·sofa