๐ŸŒŠ Beyond Rigid Bodies#

Genesis unified multiple physics solvers and supports simulation beyond rigid body dynamics. A solver is essentially a set of physics simulation algorithms to handle a specific set of materials. In this tutorial, we will go through 3 popular solvers and use them to simulate entities with different physical properties:

Liquid simulation using SPH Solver #

First, letโ€™s see how we can simulate a water cube. Letโ€™s create an empty scene and add a plane as usual:

import genesis as gs

########################## init ##########################
gs.init()

########################## create a scene ##########################

scene = gs.Scene(
    sim_options=gs.options.SimOptions(
        dt       = 4e-3,
        substeps = 10,
    ),
    sph_options=gs.options.SPHOptions(
        lower_bound   = (-0.5, -0.5, 0.0),
        upper_bound   = (0.5, 0.5, 1),
        particle_size = 0.01,
    ),
    vis_options=gs.options.VisOptions(
        visualize_sph_boundary = True,
    ),
    show_viewer = True,
)

########################## entities ##########################
plane = scene.add_entity(
    morph=gs.morphs.Plane(),
)

A few things to we should pay attention to here:

  • When configuring sim_options, now we are using a relatively small dt with substeps=10. This means inside the simulator, for each step, it will simulate 10 substeps, each with substep_dt = 4e-3 / 10. When we were dealing with rigid bodies earlier, we didnโ€™t need to set this and simply used the default setting (substeps=1), which only runs 1 substeps in each step. Next, letโ€™s add water. Adding

  • As we discussed before, we use options to configure each different solver. Since we are using SPHSolver, we need to configure its properties via sph_options. In this example, we set the boundary of the solver, and specified the particle size to be 0.01m. SPHSolver is a lagrangian solver, meaning it uses particles to represent objects.

  • In vis_options, we specified that we would like to see the boundary of the SPH Solver in the rendered view.

Next, letโ€™s add a water block entity and start the simulation! When we add the block, the only difference we need to make to turn it from a rigid block to a water block is setting the material. In fact, earlier when we were dealing with only rigid bodies, this was internally set to be gs.materials.Rigid() by default. Since we are now using the SPH Solver for liquid simulation, we select the Liquid material under the SPH category:

liquid = scene.add_entity(
    material=gs.materials.SPH.Liquid(
        sampler='pbs',
    ),
    morph=gs.morphs.Box(
        pos  = (0.0, 0.0, 0.65),
        size = (0.4, 0.4, 0.4),
    ),
    surface=gs.surfaces.Default(
        color    = (0.4, 0.8, 1.0),
        vis_mode = 'particle',
    ),
)

########################## build ##########################
scene.build()

horizon = 1000
for i in range(horizon):
    scene.step()

When creating the Liquid material, we set sampler='pbs'. This configures how we want to sample particles given the Box morph. pbs stands for โ€˜physics-based samplingโ€™, which runs some extra simulation steps to make sure the particles are arranged in a physically natural way. You can also use 'regular' sampler to sample the particles simply using a grid lattice pattern. If you are using other solvers such as MPM, you can also use 'random' sampler.

You may also note that we passed in an extra attribute โ€“ surface. This attribute is used to define all the visual properties of the entity. Here, we set the color of the water to be blueish, and chose to visualize it as particles by setting vis_mod='particle'.

Once you run this successfully, you will see the water drops and spreads over the plane, but constrained within the solver boundary:

You can get the real-time particle positions by:

particles = liquid.get_particles()

Changing the liquid properties: You can also play with the physical properties of the liquid. For example, you can increase its viscosity (mu) and surface tension (gamma):

material=gs.materials.SPH.Liquid(mu=0.02, gamma=0.02),

and see how the behavior will be different. Enjoy!

The complete script:

import genesis as gs

########################## init ##########################
gs.init()

########################## create a scene ##########################

scene = gs.Scene(
    sim_options=gs.options.SimOptions(
        dt       = 4e-3,
        substeps = 10,
    ),
    sph_options=gs.options.SPHOptions(
        lower_bound   = (-0.5, -0.5, 0.0),
        upper_bound   = (0.5, 0.5, 1),
        particle_size = 0.01,
    ),
    vis_options=gs.options.VisOptions(
        visualize_sph_boundary = True,
    ),
    show_viewer = True,
)

########################## entities ##########################
plane = scene.add_entity(
    morph=gs.morphs.Plane(),
)

liquid = scene.add_entity(
    # viscous liquid
    # material=gs.materials.SPH.Liquid(mu=0.02, gamma=0.02),
    material=gs.materials.SPH.Liquid(),
    morph=gs.morphs.Box(
        pos  = (0.0, 0.0, 0.65),
        size = (0.4, 0.4, 0.4),
    ),
    surface=gs.surfaces.Default(
        color    = (0.4, 0.8, 1.0),
        vis_mode = 'particle',
    ),
)

########################## build ##########################
scene.build()

horizon = 1000
for i in range(horizon):
    scene.step()

# get particle positions
particles = liquid.get_particles()

Deformable object simulation using MPM Solver #

MPM solver is a very powerful physics solver that supports a wider range of materials. MPM stands for material point method, and uses a hybrid lagrangian-eulerian representation, i.e. both particles and grids, to represent objects.

In this example, letโ€™s create three objects:

  • An elastic cube, visualized as 'particles'

  • A liquid cube, visualized as 'particles'

  • An elastoplastic sphere, visualized as the original sphere mesh, but deformed based on the internal particle state (vis_mode='visual'). Such process that maps internal particle state to a deformed visual mesh is called skinning in computer graphics.

Complete code script:

import genesis as gs

########################## init ##########################
gs.init()

########################## create a scene ##########################

scene = gs.Scene(
    sim_options=gs.options.SimOptions(
        dt       = 4e-3,
        substeps = 10,
    ),
    mpm_options=gs.options.MPMOptions(
        lower_bound   = (-0.5, -1.0, 0.0),
        upper_bound   = (0.5, 1.0, 1),
    ),
    vis_options=gs.options.VisOptions(
        visualize_mpm_boundary = True,
    ),
    viewer_options=gs.options.ViewerOptions(
        camera_fov=30,
    ),
    show_viewer = True,
)

########################## entities ##########################
plane = scene.add_entity(
    morph=gs.morphs.Plane(),
)

obj_elastic = scene.add_entity(
    material=gs.materials.MPM.Elastic(),
    morph=gs.morphs.Box(
        pos  = (0.0, -0.5, 0.25),
        size = (0.2, 0.2, 0.2),
    ),
    surface=gs.surfaces.Default(
        color    = (1.0, 0.4, 0.4),
        vis_mode = 'visual',
    ),
)

obj_sand = scene.add_entity(
    material=gs.materials.MPM.Liquid(),
    morph=gs.morphs.Box(
        pos  = (0.0, 0.0, 0.25),
        size = (0.3, 0.3, 0.3),
    ),
    surface=gs.surfaces.Default(
        color    = (0.3, 0.3, 1.0),
        vis_mode = 'particle',
    ),
)

obj_plastic = scene.add_entity(
    material=gs.materials.MPM.ElastoPlastic(),
    morph=gs.morphs.Sphere(
        pos  = (0.0, 0.5, 0.35),
        radius = 0.1,
    ),
    surface=gs.surfaces.Default(
        color    = (0.4, 1.0, 0.4),
        vis_mode = 'particle',
    ),
)


########################## build ##########################
scene.build()

horizon = 1000
for i in range(horizon):
    scene.step()

Note that to change the underlying physical material, all you have to do is to change the material attribute. Feel free to play with other material types (such as MPM.Sand() and MPM.Snow()), as well as the property values in each material type.

Expected rendered result:

Cloth simulation with PBD Solver #

PBD stands for Position Based Dynamics. This is also a lagrangian solver that represents entities using particles and edges, and simulates their state by solving a set of position-based constraints. It can be used to simulate 1D/2D/3D entities that preserve their topologies. In this example, we will see how to simulate cloth with PBD solver.

In this example, we will add two square-shape cloth entities: one with 4 corners fixed, the other with only 1 corner fixed and falls down onto the first piece of cloth. In addition, we will render them using different vis_modes.

Create the scene and build:

import genesis as gs

########################## init ##########################
gs.init()

########################## create a scene ##########################

scene = gs.Scene(
    sim_options=gs.options.SimOptions(
        dt       = 4e-3,
        substeps = 10,
    ),
    viewer_options=gs.options.ViewerOptions(
        camera_fov = 30,
        res        = (1280, 720),
        max_FPS    = 60,
    ),
    show_viewer = True,
)

########################## entities ##########################
plane = scene.add_entity(
    morph=gs.morphs.Plane(),
)

cloth_1 = scene.add_entity(
    material=gs.materials.PBD.Cloth(),
    morph=gs.morphs.Mesh(
        file='meshes/cloth.obj',
        scale=2.0,
        pos=(0, 0, 0.5), 
        euler=(0.0, 0, 0.0),
    ),
    surface=gs.surfaces.Default(
        color=(0.2, 0.4, 0.8, 1.0),
        vis_mode='visual',
    )
)

cloth_2 = scene.add_entity(
    material=gs.materials.PBD.Cloth(),
    morph=gs.morphs.Mesh(
        file='meshes/cloth.obj',
        scale=2.0,
        pos=(0, 0, 1.0), 
        euler=(0.0, 0, 0.0),
    ),
    surface=gs.surfaces.Default(
        color=(0.8, 0.4, 0.2, 1.0),
        vis_mode='particle',
    )
)

########################## build ##########################
scene.build()

Then, letโ€™s fix the corners (particles) we want. To do this, we provide a handy tool to locate a particle using a location in the cartesian space:


cloth_1.fix_particle(cloth_1.find_closest_particle((-1, -1, 1.0)))
cloth_1.fix_particle(cloth_1.find_closest_particle((1, 1, 1.0)))
cloth_1.fix_particle(cloth_1.find_closest_particle((-1, 1, 1.0)))
cloth_1.fix_particle(cloth_1.find_closest_particle((1, -1, 1.0)))

cloth_2.fix_particle(cloth_2.find_closest_particle((-1, -1, 1.0)))

horizon = 1000
for i in range(horizon):
    scene.step()

Expected rendered result:

Warning

Skinning for 2D meshes

We noticed some issues when using a 2D flat cloth mesh and set vis_mode='visual', this is due to degenerated pseudo-inverse matrix computation when computing the barycentric weights. You may notice weird visualization results in the above example if you add a non-zero euler to the cloth and use vis_mode='visual'. We will fix this very soon.

More tutorials on inter-solver coupling coming soon!