4-SOFA仿真 从网格文件里读入点_刚体点云_ 4-loader-and-topology.scn

XML 复制代码
<?xml version="1.0"?>

<!-- Loader and topology -->
<Node name="root" dt="0.01" gravity="0 0 0">

    <RequiredPlugin name="Sofa.Component.Mass"/> <!-- Needed to use components [UniformMass] -->
    <RequiredPlugin name="Sofa.Component.MechanicalLoad"/> <!-- Needed to use components [ConstantForceField] -->
    <RequiredPlugin name="Sofa.Component.StateContainer"/> <!-- Needed to use components [MechanicalObject] -->
    <RequiredPlugin name="Sofa.Component.LinearSolver.Iterative"/> <!-- Needed to use components [CGLinearSolver] -->
    <RequiredPlugin name="Sofa.Component.ODESolver.Backward"/> <!-- Needed to use components [EulerImplicitSolver] -->
    <RequiredPlugin name="Sofa.Component.IO.Mesh"/> <!-- Needed to use components [MeshGmshLoader] -->
    <RequiredPlugin name="Sofa.Component.Topology.Container.Dynamic"/> <!-- Needed to use components [PointSetTopologyContainer] -->

    <DefaultAnimationLoop computeBoundingBox="false"/>

    <MeshGmshLoader name="meshLoaderCoarse" filename="mesh/liver.msh" />

    <!--
    <TransformEngine name="transformer"
                     input_position="@meshLoaderCoarse.position"
                     translation="0 2 0"
                     rotation="0 0 0" />
    -->

    <!-- position="@../transformer.output_position" -->

    <Node name="Liver">

        <EulerImplicitSolver />
        <CGLinearSolver iterations="200" tolerance="1e-09" threshold="1e-09"/>

        <PointSetTopologyContainer name="topo" src="@../meshLoaderCoarse" />

        <MechanicalObject template="Rigid3d" name="MechanicalModel" showObject="1"/>

        <UniformMass totalMass="1" />
        <ConstantForceField totalForce="1 0 0 0 0 0" />

    </Node>

</Node>

简化学习版:

XML 复制代码
<Node name="root" dt="0.01" gravity="0 0 0">

    <MeshGmshLoader name="meshLoaderCoarse" filename="mesh/liver.msh" />

    <Node name="Liver">

        <EulerImplicitSolver />
        <CGLinearSolver iterations="200" tolerance="1e-09" threshold="1e-09"/>

        <PointSetTopologyContainer name="topo" src="@../meshLoaderCoarse" />

        <MechanicalObject template="Rigid3d" name="MechanicalModel" showObject="1"/>

        <UniformMass totalMass="1" />
        <ConstantForceField totalForce="1 0 0 0 0 0" />

    </Node>
</Node>

这部分核心新增的是:

XML 复制代码
<MeshGmshLoader name="meshLoaderCoarse" filename="mesh/liver.msh" />

1.MeshGmshLoader

MeshGmshLoader是一个 mesh loader,网格加载器。作用是:从 mesh/liver.msh 文件里读取网格信息。

这里的 .msh 是 Gmsh 软件常用的网格格式,所以组件叫 MeshGmshLoader。

loader 的工作方式基本都一样:在仿真初始化时,把文件里的 topology information 读出来。

2.Loader 读取的是什么:topology information

MeshGmshLoader 不是在每个 time step 都算东西。它主要在初始化时读取文件,读完以后就比较"被动"了。

它会从 .msh 文件中读取:点的数量、每个点的位置、边的信息、三角形由哪些点连接、四面体由哪些点连接。

它只是把这些数据加载出来,让后面的组件可以使用。初始化完成后,loader 的主要工作就结束了。

MeshGmshLoader = 读文件的人。它负责把 liver.msh 里的网格信息读进 SOFA,但它本身不负责物理计算。

3.为什么 loader 放在 root 下面

最好把 loader 放在场景比较靠上的位置,比如 root 下面,这样后面的节点都可以方便地引用它。

这就是为什么后面 Liver 节点里面可以写:

XML 复制代码
<PointSetTopologyContainer name="topo" src="@../meshLoaderCoarse" />

这里的 @../meshLoaderCoarse 就是在引用 root 下的 loader。

4.PointSetTopologyContainer

进入 Liver 节点后,新增了:

XML 复制代码
<Node name="Liver">

    <PointSetTopologyContainer name="topo" src="@../meshLoaderCoarse" />

</Node>

这个组件叫 PointSetTopologyContainer,点集拓扑容器。它的作用是:从 meshLoaderCoarse 里拿到点的信息,并保存成当前节点可用的拓扑数据。

src 是 source 的意思:src="@../meshLoaderCoarse"

  • @ 表示引用 SOFA 场景里的另一个组件
  • .. 表示上一级节点
  • meshLoaderCoarse 是上面那个 loader 的名字

所以这句话意思是:把上一级节点里的 meshLoaderCoarse 作为数据源。

container 会去 loader 里检查可用信息;因为这里用的是 PointSetTopologyContainer,所以它只关心点,不关心边、三角形、四面体。

5.PointSet:只拿"点",不拿完整网格

.msh 文件里可能有很多东西:nodes / points、edges、triangles、tetrahedra

但现在这个组件是:<PointSetTopologyContainer ... />

所以它只拿最低层级的信息:点的位置和点的列表

我们现在只关心 nodes / points,不关心 edges,不关心 triangles。

所以这一阶段看到的画面会像一堆小坐标架/点云,而不是完整的肝脏表面或实体网格。

6.MechanicalObject 里没有 position

前面单个刚体粒子是这样写的:

XML 复制代码
<MechanicalObject template="Rigid3d"
                  name="myParticle"
                  position="0 0 0 0 0 0 1"
                  showObject="1" />

但现在新文件里是:

XML 复制代码
<MechanicalObject template="Rigid3d"
                  name="MechanicalModel"
                  showObject="1" />

MechanicalObject 里什么东西不见了?答案就是 position 这个 data field 不见了。

原因是:现在位置不是手写在 MechanicalObject 里了,而是从 mesh 文件里读进来。

7.SOFA 会自动把 topology 里的点位置复制给 MechanicalObject

初始化时发生的事情是:

MeshGmshLoader 读取 liver.msh

→ PointSetTopologyContainer 从 loader 拿到点信息

→ MechanicalObject 在同一个 node 里找到 topology container

→ 自动用 topology 中的点位置初始化自己的 position

如果 TopologyContainerMechanicalObject 在同一个 node 里,MechanicalObject 会自动找这个 container,并用它加载出来的位置初始化自己的 position data。

所以虽然代码里没有写 position="..." ,但 SOFA 会自动补上来自网格文件的点位置。

即使手动写上某些 position 引用,或者删掉它,结果也一样,因为 MechanicalObject 会自动连接到同节点里的 topology container。

8.loader + topology + mechanical object 的基本关系。

(1)MeshGmshLoader

读取 mesh 文件

(2)PointSetTopologyContainer

从 loader 中提取点拓扑

(3)MechanicalObject

用这些点的位置初始化自己的状态向量

从这里开始,SOFA 不再靠你手写每个点的位置,而是通过 MeshLoader 读取网格文件,再通过 TopologyContainer 把点信息传给 MechanicalObject。

注释代码

XML 复制代码
<!--
<TransformEngine name="transformer"
                 input_position="@meshLoaderCoarse.position"
                 translation="0 2 0"
                 rotation="0 0 0" />
-->
<!-- position="@../transformer.output_position" -->

9.TransformEngine

如果你加载进来的 mesh 位置不合适,比如太低、太偏、不在你想要的位置,你可以对这个 mesh 做一个变换。这里用的组件叫 TransformEngine

Engine 可以理解成 SOFA 里的"数据处理器":输入 input→ 做某种处理→ 输出 output

这里它做的处理是:translation:平移 rotation:旋转

XML 复制代码
input_position="@meshLoaderCoarse.position"
translation="0 2 0"

所以这段代码的意思是:把 meshLoaderCoarse 读出来的点位置作为输入,然后沿 Y 方向平移 2

10.TransformEngine 不改变拓扑,只改变位置配置

这个变换不会改变 topology。

也就是说:点和点怎么连接,不变;有多少个点,不变;边/三角形/四面体关系,不变。

变的是:这些点在空间中的初始位置。

你可以给 MechanicalObject 提供一个新的 configuration,也就是新的初始 position / orientation。

MeshGmshLoader 读入原始点位置

TransformEngine 把这些点整体平移/旋转

MechanicalObject 使用变换后的点位置

11.position="@../transformer.output_position"

如果取消注释,可能会在 MechanicalObject 里写:

XML 复制代码
<MechanicalObject template="Rigid3d"
                  name="MechanicalModel"
                  position="@../transformer.output_position"
                  showObject="1" />

@../transformer.output_position 意思是:

去上一级节点找 transformer 这个组件,

然后使用它的 output_position 作为 MechanicalObject 的 position。

TransformEngine 先接收:input_position="@meshLoaderCoarse.position"

再输出:transformer.output_position

然后 MechanicalObject 可以把这个输出作为自己的初始位置。

12.不写 position 也能运行,因为 SOFA 会自动从 topology 复制

即使不写 position="@../transformer.output_position" 也能运行。

因为 PointSetTopologyContainer 里面已经有点的位置,MechanicalObject 会在初始化时自动把这些点位置复制过来。

所以这两种方式区别是:

(1)不写 position:

MechanicalObject 用 topology 里的原始点位置。

(2)写 position="@../transformer.output_position":

MechanicalObject 用经过 TransformEngine 平移/旋转后的点位置。

13.为什么很多粒子后,速度还是一样

现在不再是一个粒子,而是很多个小刚体 frame / 点了。但此时速度的值还是跟时间的值一样。

因为 totalMass="1" 是总质量,SOFA 会把总质量均匀分配到所有节点上;totalForce="1 0 0 0 0 0" 也是总力,也会分配到所有节点上。

复制代码
相关推荐
波诺波5 天前
SOFA软件操作说明
sofa
波诺波5 天前
SOFA的介绍
sofa
老马啸西风1 年前
SOFABoot-05-依赖管理
算法·spring·微服务·云原生·中间件·springboot·sofa
bug菌¹3 年前
Spring Boot进阶(91):从零开始,轻松打造Sofa+Spring Boot分布式开发环境
java·spring boot·sofa