粒子发射器#

发射器(emitter)是一个喷嘴,在仿真运行过程中把粒子注入粒子求解器。超越刚体 在构建时一次性填充固定体积的粒子,而发射器每一步都注入新粒子:像水龙头、软管或持续流动的沙流,只要你调用它,它就会一直喷出。

发射器本身不进行任何仿真。它拥有一个粒子实体,该实体用 max_particles 个槽位预分配,发射器把位置和速度写入该实体的求解器。发射器的材质决定由哪个求解器处理这些粒子:gs.materials.SPH.* 对应光滑粒子流体动力学,gs.materials.MPM.* 对应物质点法,gs.materials.PBD.* 对应基于位置的动力学。

完整脚本是 examples/coupling/water_wheel.py:

import numpy as np
import genesis as gs

gs.init()

scene = gs.Scene(
    sim_options=gs.options.SimOptions(dt=4e-3, substeps=10),
    sph_options=gs.options.SPHOptions(particle_size=0.02),  # meters
)

scene.add_entity(gs.morphs.Plane())

emitter = scene.add_emitter(
    material=gs.materials.SPH.Liquid(),
    max_particles=100000,
    surface=gs.surfaces.Glass(color=(0.7, 0.85, 1.0, 0.7)),
)

scene.build()

for i in range(500):
    emitter.emit(
        pos=(0.5, 1.0, 3.5),          # nozzle position, meters, Z-up
        direction=(0.0, 0.0, -1.0),   # aimed straight down
        speed=5.0,                    # m/s
        droplet_shape="circle",
        droplet_size=0.22,            # stream diameter, meters
    )
    scene.step()

添加发射器#

scene.add_emitter() 必须像其他实体一样在 scene.build() 之前调用。它会分配粒子实体并返回一个 Emitter 句柄,供你每一步调用:

emitter = scene.add_emitter(
    material=gs.materials.SPH.Liquid(),
    max_particles=100000,
    surface=gs.surfaces.Glass(color=(0.7, 0.85, 1.0, 0.7)),
)
  • material 选择求解器和物理行为。可传入任何 MPM 或 SPH 材质(例如 gs.materials.SPH.Liquid、gs.materials.MPM.Liquid 或 gs.materials.MPM.Sand),或 gs.materials.PBD.Particle 与 gs.materials.PBD.Liquid 之一。其他材质会在 add_emitter 时报错。

  • max_particles 限制发射器容纳的粒子数。一旦发射达到上限,发射器会回收最旧的粒子,因此长时间运行的流在内存上保持有界。默认值为 20000。

  • surface 只控制外观。若省略,发射器使用 gs.surfaces.Default(color=(0.6, 0.8, 1.0, 1.0))。发射器不支持 vis_mode="visual";请使用默认的 "particle",或用 "recon" 渲染重建的流体表面。

每步发射#

发射是逐步进行的:一次 emit() 调用注入流的一段,每一步都调用便形成连续流动。停止调用后流会中断,而已发射的粒子继续仿真。

emitter.emit(
    pos=(0.5, 1.0, 3.5),
    direction=(0.0, 0.0, -1.0),
    speed=5.0,
    droplet_shape="circle",
    droplet_size=0.22,
)

pos 是世界坐标系中的喷嘴位置,单位为米(Z 轴向上)。direction 会在内部归一化,因此只有方向重要,长度无关。speed 是发射速度,单位为 m/s,同时设定每个粒子沿 direction 的初速度。

当你不设置 droplet_length 时,发射器会根据一步内的运动 speed * dt 计算每段长度。若该长度短于一个粒子,发射器会累积它,在积累足够之前不发射任何粒子,因此慢速流仍会形成完整粒子而不会被丢弃。

液滴形状#

droplet_shape 设置发射几何体的横截面;droplet_size 设置其范围,单位为米。

droplet_shape

droplet_size

发射内容

"circle"

float(直径)

沿 direction 的圆柱形流。

"sphere"

float(直径)

单个球形液滴;忽略 droplet_length。

"square"

float(边长)

沿 direction 的方形截面流。

"rectangle"

(width, height)

沿 direction 的矩形截面流。

theta 以弧度为单位让液滴绕其发射轴旋转。p_size 覆盖采样粒子尺寸(默认为求解器的设置)。

随时间改变方向会让流弯曲。examples/coupling/sand_wheel.py 就以这种方式让沙流来回扫动:

for i in range(1000):
    emitter.emit(
        pos=(0.5, 0.0, 2.3),
        direction=(0.0, np.sin(i / 10) * 0.35, -1.0),  # oscillating aim
        speed=8.0,
        droplet_shape="rectangle",
        droplet_size=[0.03, 0.05],  # width, height in meters
    )
    scene.step()

全向发射#

emit_omni() 从球壳沿半径方向释放粒子,而不是从定向喷嘴喷出:这是一次爆发或喷泉,而非一股流。每个粒子的速度都以 speed 从中心指向外:

emitter.emit_omni(
    pos=(0.5, 0.5, 1.0),   # center of the source, meters
    source_radius=0.1,     # shell radius, meters
    speed=2.0,             # m/s, radially outward
)

多个发射器#

每个发射器拥有自己的粒子实体,因此可以按需添加多个:不同材质、位置和表面可在同一场景中共存。在同一步循环中对每个发射器调用 emit()。

emitter_a = scene.add_emitter(
    material=gs.materials.MPM.Liquid(),
    max_particles=500000,
    surface=gs.surfaces.Rough(color=(0.0, 0.9, 0.4, 1.0)),
)
emitter_b = scene.add_emitter(
    material=gs.materials.MPM.Liquid(),
    max_particles=500000,
    surface=gs.surfaces.Rough(color=(0.0, 0.4, 0.9, 1.0)),
)

for i in range(500):
    emitter_a.emit(pos=(0.3, 0.5, 2.0), direction=(0, 0, -1), speed=3.0, droplet_shape="circle", droplet_size=0.1)
    emitter_b.emit(pos=(0.7, 0.5, 2.0), direction=(0, 0, -1), speed=3.0, droplet_shape="circle", droplet_size=0.1)
    scene.step()

注意事项与常见陷阱#

Warning

发射器必须位于其求解器的仿真域内。如果某次 emit() 调用会把粒子放到求解器边界之外,它会报错而不是静默裁剪。请把喷嘴对准 mpm_options / sph_options 设定的边界内部。

Note

发射器不支持可微分模式。以 requires_grad=True 构建场景后再添加发射器会抛出异常。

另请参阅#

  • 超越刚体:发射出的粒子所进入的 SPH、MPM 和 PBD 求解器。

  • 软体机器人:基于相同粒子求解器构建的肌肉驱动软体和混合实体。