Skip to content

体与器

位点(Locus)

The place where something is situated or occurs. ―― Cambridge Dictionary

在多体动力学中,准确描述物体上各个物质点的位置和方位是建立连接、施加外力和处理碰撞的基础。此外,在处理接触和碰撞时,这些点表面的法线方向、摩擦系数和回弹系数等物理属性也至关重要。

为了统一且高效地管理这些信息,Rible.jl 引入了位点(Locus位点状态(LocusState类型。其中Locus 是一个immutable类型,用于在物体的局部坐标系中定义一个物质点的不变属性:

  • position:该点在局部坐标系中的位置向量。

  • axes:该点处的局部参考轴系(Axes),通常用于定义接触面的法线方向或关节的旋转/平移轴。默认情况下,第一根轴(x轴)即为接触法向。

  • friction_coefficientrestitution_coefficient:分别定义该点处的摩擦系数和碰撞回弹系数。

构造位点

在构建一般刚体时,我们需要提供质心的 mass_locus 以及一系列用于后续连接或接触的位点 loci 向量。

julia
# 定义局部坐标系下的位点 (Locus),例如手柄端点和锤头中心
mass_locus = RB.Locus(SVector(-0.2, 0.0, 0.0)) # 质心坐标,默认无摩擦/回弹

# 定义两个位点。由于构造函数的灵活分派,我们可以按需提供参数
loci = [
    # 位点1:指定位置和参考轴
    RB.Locus(SVector(0.0, 0.0, 0.0), RB.Axes(SVector(1.0, 0.0, 0.0))), 
    # 位点2:指定位置、参考轴、摩擦系数(0.5)和回弹系数(0.1)
    RB.Locus(SVector(0.5, 0.0, 0.0), RB.Axes(SVector(1.0, 0.0, 0.0)), 0.5, 0.1)  
]

LocusState 则是mutable类型,记录了该点在仿真过程中的全局(世界)坐标系状态:

  • frame:该点在全局坐标系下的完整运动学框架(CartesianFrame),包括它当前的全局位置、全局姿态(旋转矩阵)、全局线速度和全局角速度。

  • forcetorque:当前作用在该点上的全局力和力矩。

  • contact_state:维护当前点的接触状态,例如是否正处于激活的接触中、接触间隙(gap)以及接触力等。

体(Body)

体用一源,显微无间。 ―― 程颐《易传序》

体(Body)是多体系统的基础构建块。在 Rible.jl 中,所有的体都是抽象类型 AbstractBody{N,T} 的子类,其中 N 代表空间维度(2或3),T 代表数值类型(通常为 Float64)。这种参数化设计确保了系统既能处理平面问题也能处理空间问题,同时保持类型安全和高性能。

刚体 (RigidBody)

刚体(RigidBody)是多体系统中最基本的单元,在Rible.jl中它由RigidBodyPropertyRigidBodyState、坐标描述(Coordinates)和用于可视化的 Mesh 共同构成。

首先,通过 RigidBodyProperty 定义刚体的不变量。这包括质量属性(质量、惯量)和几何特征(质心位置、位点 Loci)。 一个刚体的物理特性由 RigidBodyProperty 定义,它不仅包含动力学参数,还包含仿真所需的元数据。动力学参数主要涉及质量(mass)和惯性张量(inertia),其中惯性张量通常存储为 SMatrix 以优化内存和计算速度。 此外,每个刚体都有唯一的 id 和可选的 type(符号标记)。 布尔字段 contactablevisible 分别控制刚体是否参与碰撞检测以及是否在可视化中渲染。

julia
id = 1
mass = 2.5
inertia = SMatrix{3,3}(I)  # 单位惯性矩阵

prop = RB.RigidBodyProperty(
    id, 
    true,           # contactable
    mass, 
    inertia,
    mass_locus, 
    loci;
    visible=true
)

刚体的瞬时状态由 RigidBodyState 维护。 这包含了在全局坐标系中的位置矢量和表示方向的旋转矩阵(或四元数),以及线速度和角速度。这些状态量是积分器在每个时间步更新的对象。接下来,RigidBodyState 存储随时间变化的量。需要提供初始时刻的全局位置、姿态(旋转矩阵)、线速度和角速度。

julia
r0 = SVector(0.0, 0.0, 0.0)      # 初始位置
R = SMatrix{3,3}(I)              # 初始姿态 (无旋转)
r_dot = zero(r0)                 # 初始线速度
omega = zero(r0)                 # 初始角速度
state = RB.RigidBodyState(prop, r0, R, r_dot, omega)

然后是选择坐标描述。系统使用节点坐标法 (NCF) 来描述运动。根据物体的类型(如自由浮动的块、连接两点的连杆),选择合适的坐标公式。例如,NC1P3V(1点3矢量)适用于标准的6自由度刚体,NC3D2P(3D 2点)适用于由两个端点定义的连杆,而 NC2D2P 则用于平面内的连杆。

julia
# 对于一般的空间刚体,使用一个基准点和三个方向矢量 (NC1P3V)
coords = RB.NCF.NC1P3V(r0, r0, R)
NC{3, 3, Float64, 9, 12, 144}(1, 3, LNCData{3, 3, Float64, 9}([0.0, 0.0, 0.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0], [1.0 0.0 0.0; 0.0 1.0 0.0; 0.0 0.0 1.0]), [1 0 … 0 0; 0 1 … 0 0; … ; 0 0 … 1 0; 0 0 … 0 1], [0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 1 0; 0 0 … 0 1], LinearAlgebra.Symmetric{Int64, SparseArrays.SparseMatrixCSC{Int64, Int64}}[[0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0], [0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0], [0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 2 0; 0 0 … 0 2], [0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0], [0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0], [0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]])

最后,为了在仿真中进行渲染,可以加载 STL 模型或生成几何图元。

julia
# 加载 STL 文件并进行缩放/平移 (示例)
# mesh = load(RB.assetpath("link.STL")) |> RB.make_patch(; scale=0.1)
mesh = nothing # 此处省略详细网格
julia
# 最终组装实体
body = RB.RigidBody(prop, state, coords, mesh)

可视化:

julia
# vis(body)

柔体(Flexible)

柔体(FlexibleBody)用于模拟发生大变形的物体,如绳索、软体机械臂或薄板。Rible.jl 基于绝对节点坐标公式(ANCF) 实现柔性体动力学。

与刚体不同,柔体的状态不由单一的质心位置和姿态决定,而是由一系列节点的绝对坐标位置梯度定义。为了高效处理大量的自由度,系统使用 FlexibleBodyCoordinatesCache 来预计算和存储形函数、质量矩阵积分和其他几何中间量。柔体的动力学行为由多种内部和外部力共同驱动: - 弹性力:通过 strain! 函数计算,基于材料的应变能产生抵抗变形的力。 - 体积力:如重力,通过 potential_energy_field 计算。 - 惯性力:包括离心力(centrifugal_force!)和其他由于参考系运动产生的力。

为了在精度和效率之间取得平衡,柔体支持通过 subdivide 函数进行网格细分。将一个柔性体划分为多个单元段,可以更精准地捕捉复杂的弯曲和扭转模式。

器 (Apparatus)

大器免成。 ――《老子》帛书本

器(Apparatus)是系统中连接体、施加约束和力的核心组件。它不仅代表物理上的关节或弹簧,更是多体动力学方程中约束项力项的载体。

每个 Apparatus 对象都维护着一个 ApparatusCache,用于存储动力学求解过程中的关键矩阵和向量:描述系统几何约束的代数方程值(约束方程 (\Phi)),约束方程对广义坐标的偏导数矩阵(雅可比矩阵 (A)),由该装置产生的力(如弹簧力或驱动力)在广义坐标上的投影(广义力 (Q)),以及包括 (\dot{A}\dot{q}) 等用于加速度级约束的导数项。对于复杂的集群系统(如多段索网),还使用 ApparatusClusterCache 来管理段数、刚度系数以及状态导数矩阵,以支持批量的矩阵运算。

力(Force)

力(Force)模块负责计算作用在体上的广义力。所有力模型均继承自 AbstractForce

基础力元包括距离弹簧阻尼器 (DistanceSpringDamper),这是最基础的力元,连接两个点(Loci),维护着 DistanceSpringDamperState 以实时跟踪当前的长度、伸长率和张力。旋转/扭转弹簧 (Torsional/RotationalSpringDamper) 则用于产生恢复力矩,通常作用于关节的转轴上,抵抗相对旋转。

对于更复杂的需求,系统提供了高级力模型。例如,形状记忆合金 (SMA) (SMADistanceSpringDamper) 引入了温度变量,模拟 SMA 材料在相变过程中的非线性应力-应变关系和迟滞效应。此外,通过分段与集群 (DistanceSpringDamperSegmentClusterDistanceSpringDampers),可以将一条长索或复杂结构建模为多个串联的弹簧阻尼段,系统会自动处理段与段之间的相互作用和节点滑动(SlidingPoint)。

系统通过 potential_energy 接口统一管理所有力元的势能计算,这对于基于能量的验证和控制算法至关重要。

铰接(Joint)

铰接(Joint)是限制物体相对运动的几何约束。Rible.jl 采用自然坐标公式(NCF)(以及相应的系统配置机制),这意味着许多关节的约束方程是关于节点坐标的线性或简单二次函数,极大地简化了雅可比矩阵的计算。

在代码底层,关节通过 LinearJointProtoJoint 类型体实现。许多关节可以通过常数矩阵 (A) 表示为 (A \cdot q = 0) 的形式,LinearJoint 存储了这个矩阵 (A) 以及约束违反量。get_joint_idx 函数负责从相关联的体中提取坐标索引,区分“自由坐标”和“约束坐标”,确保求解器正确组装系统矩阵。

构建关节的过程是对拓扑关系的确切描述。首先是Signifier(标识符)Signifier(body, point_idx, [axis_idx]) 唯一确定了物体上的某个点或轴。其次是Hen2Egg(连接对)Hen2Egg(hen, egg) 定义了从“母体”为主导到“子体”被带动的连接方向。这种父子连接图关系有助于构建树状或链状系统的拓扑构型。

系统支持多种标准关节及其变体(如平面 Planar、轨道 Orbital 等):

关节类型自由度说明
:Fixed0固定关节。将两个刚体完全锁定,或将刚体上的某点锁定在空间固定位置(FixedPointJoint)。常用于基座固定。
:Revolute1转动关节。允许绕共用轴旋转。需要指定两个物体上的点重合以及轴线共线。
:Prismatic1移动关节。允许沿轴线平移,限制旋转。
:Cylindrical2圆柱关节。允许绕轴旋转和沿轴滑动。
:Universal2万向节。由两个正交的转动轴组成,允许两个方向的旋转。
:Spherical3球关节。仅约束平动,允许绕连接点任意旋转。

此外,CableJoint 提供了基于绳索长度的单边约束((l \le l_{max})),而 ClusterJoint 则用于处理多体之间复杂的耦合约束。对于非标准构型场景,还提供 Planar(平面)、Orbital(轨道)等变体以适应特殊约束需求。

构建一个关节的核心在于清楚描述“连接关系”。这通过以下两个类型实现:

  1. 标识符 (Signifier):用于精确定位连接点或参考系。
  • Signifier(body, point_id):指定物体上的某个位点(Locus)。

  • Signifier(body, point_id, axis_id):进一步指定该点上的某个轴方向(用于转动关节等约束对齐的要求)。

  1. 连接对 (Hen2Egg):定义“母体”(Hen)与“子体”(Egg)的配对。
  • Hen2Egg(hen_signifier, egg_signifier):建立两个标识符之间的对应关系。
julia
prop1 = RB.RigidBodyProperty(1, true, 1.0, inertia, mass_locus, loci)
state1 = RB.RigidBodyState(prop1, r0, R, r_dot, omega)
nmcs1 = RB.NCF.NC1P3V(r0, r0, R)
body1 = RB.RigidBody(prop1, state1, nmcs1, nothing)

prop2 = RB.RigidBodyProperty(2, true, 1.0, inertia, mass_locus, loci)
r0_2 = r0 + SVector(1.0, 0.0, 0.0)
state2 = RB.RigidBodyState(prop2, r0_2, R, r_dot, omega)
nmcs2 = RB.NCF.NC1P3V(r0_2, r0_2, R)
body2 = RB.RigidBody(prop2, state2, nmcs2, nothing)
julia
# 1. 定义连接点:连接 body1 的第2个位点和 body2 的第1个位点
hen = RB.Signifier(body1, 2) 
egg = RB.Signifier(body2, 1)

# 2. 建立连接关系
conn = RB.Hen2Egg(hen, egg)

如果关节需要特定的旋转轴或平移轴(例如转动关节),应在 Signifier 中指定轴的索引:

julia
# 指定 body1 上第1个点处的第1个轴 axis1 和 body2 上第2个点处的第2个轴 axis2
hen_axis = RB.Signifier(body1, 1, 1)
egg_axis = RB.Signifier(body2, 2, 2)
conn_axis = RB.Hen2Egg(hen_axis, egg_axis)

创建关节对象时,使用 ProtoJoint 函数和关节类型符号。

julia
# 创建一个球关节
joint_spherical = RB.ProtoJoint(conn, :Spherical)

# 创建一个转动关节
joint_revolute = RB.ProtoJoint(conn_axis, :Revolute)

对于将物体上的某一点固定在全局空间的特定位置,使用固定点关节 (FixedPointJoint)。

julia
# 将 body1 上的局部点 local_p 固定在全局位置
local_p = SVector(0.0, 0.0, 0.0)
joint_fixed = RB.FixedPointJoint(body1, local_p) 
# 注意: FixedPointJoint 的构造会计算建立约束时的当前位置作为固定点

以下是一个简单的综合示例,展示如何连接两个物体:

julia
# ... 初始化 body1, body2 ...

# 定义 body1 上的连接点 (假设是第2个点)
s1 = RB.Signifier(body1, 2)
# 定义 body2 上的连接点 (假设是第1个点)
s2 = RB.Signifier(body2, 1)

# 定义连接指向
connection = RB.Hen2Egg(s1, s2)

# 创建一个球关节
# 这将约束 body1 的第2个点和 body2 的第1个点在空间中位置重合
joint = RB.ProtoJoint(connection, :Spherical)