Pure Volumetric FEM Scenes
A volumetric FEM body is a tetrahedral SimplicialComplex whose vertices own
the dynamic degrees of freedom. This page uses StableNeoHookean, the standard
large-deformation solid model, without contact so that the constitutive setup
is isolated from collision behavior.
What defines a deformable solid
| Part | API | Meaning |
|---|---|---|
| Rest mesh | tetmesh(vertices, tetrahedra) |
Tetrahedral material domain and rest positions. |
| Boundary surface | label_surface, label_triangle_orient |
Extracts and orients boundary triangles for contact and export. |
| Elastic material | ElasticModuli.youngs_poisson(E, nu) |
Converts Young's modulus and Poisson ratio to Lame parameters. |
| Primary constitution | StableNeoHookean.apply_to(mesh, moduli, mass_density) |
Creates vertex state, mass, and per-tetrahedron material attributes. |
| Boundary condition | mesh.vertices()/is_fixed |
1 fixes one vertex; 0 leaves it dynamic. |
| Contact material | ContactElement.apply_to(mesh) |
Optional pairwise material identity when contact is enabled. |
The initial vertex positions are the material rest state. Unlike ABD, there is no per-body transform that moves a normal FEM body without changing that rest state. Place or transform the mesh before applying the constitution.
Complete contact-free scene
The following asset-free program drops one tetrahedron under gravity. Contact is explicitly disabled, so no collision surface, ground, or contact table is needed for the physics. The surface labels are retained because they are the normal preparation for later contact or surface export.
#include <cmath>
#include <filesystem>
#include <iostream>
#include <uipc/uipc.h>
#include <uipc/constitution/stable_neo_hookean.h>
int main()
{
using namespace uipc;
using namespace uipc::constitution;
using namespace uipc::core;
using namespace uipc::geometry;
const std::string workspace = "output/docs/fem_cpp";
std::filesystem::create_directories(workspace);
Engine engine{"cuda", workspace};
World world{engine};
auto config = Scene::default_config();
config["dt"] = 0.01;
config["gravity"] = Vector3{0.0, -9.8, 0.0};
config["contact"]["enable"] = false;
Scene scene{config};
const Float s3 = std::sqrt(3.0) / 2.0;
vector<Vector3> vertices = {Vector3{0.0, 1.0, 0.0},
Vector3{0.0, 0.0, 1.0},
Vector3{-s3, 0.0, -0.5},
Vector3{s3, 0.0, -0.5}};
vector<Vector4i> tetrahedra = {Vector4i{0, 1, 2, 3}};
auto solid = tetmesh(vertices, tetrahedra);
label_surface(solid);
label_triangle_orient(solid);
StableNeoHookean material;
auto moduli = ElasticModuli::youngs_poisson(50.0_kPa, 0.499);
material.apply_to(solid, moduli, 1000.0);
scene.objects().create("soft_tet")->geometries().create(solid);
world.init(scene);
if(!world.is_valid())
return 1;
while(world.frame() < 10)
{
world.advance();
world.retrieve();
}
std::cout << "FEM scene reached frame " << world.frame() << '\n';
return world.is_valid() ? 0 : 1;
}
"""Minimal contact-free tetrahedral FEM scene used by the FEM tutorial."""
from pathlib import Path
import numpy as np
from uipc import Logger
from uipc.core import Engine, Scene, World
from uipc.geometry import label_surface, label_triangle_orient, tetmesh
from uipc.constitution import ElasticModuli, StableNeoHookean
from uipc.unit import kPa
Logger.set_level(Logger.Level.Warn)
workspace = Path("output/docs/fem")
workspace.mkdir(parents=True, exist_ok=True)
engine = Engine("cuda", str(workspace))
world = World(engine)
config = Scene.default_config()
config["dt"] = 0.01
config["gravity"] = [[0.0], [-9.8], [0.0]]
config["contact"]["enable"] = False # isolate the volumetric FEM model
scene = Scene(config)
s3 = np.sqrt(3.0) / 2.0
vertices = np.array(
[[0.0, 1.0, 0.0], [0.0, 0.0, 1.0], [-s3, 0.0, -0.5], [s3, 0.0, -0.5]]
)
tetrahedra = np.array([[0, 1, 2, 3]])
solid = tetmesh(vertices, tetrahedra)
label_surface(solid)
label_triangle_orient(solid)
material = ElasticModuli.youngs_poisson(50.0 * kPa, 0.499)
StableNeoHookean().apply_to(solid, material, mass_density=1000.0)
scene.objects().create("soft_tet").geometries().create(solid)
world.init(scene)
assert world.is_valid()
for _ in range(10):
world.advance()
world.retrieve()
assert world.is_valid()
print(f"FEM scene reached frame {world.frame()}")
The material in this example uses E = 50 kPa, nu = 0.499, and density
1000 kg/m^3. The public Stable Neo-Hookean overload defaults to E = 120
kPa, nu = 0.49, and density 1000 kg/m^3; supplying values explicitly is
recommended because stiffness and scale are scene dependent.
For an ordinary isotropic 3D elastic material, use E > 0 and
-1 < nu < 0.5. Values near 0.5 model near-incompressibility and usually
make the linear system harder. The API rejects the singular endpoints but does
not otherwise enforce a physically meaningful material range.
Fixing vertices
Apply the FEM constitution first because it creates the is_fixed attribute,
then mark selected vertices:
Fixed vertices remain part of the deformable mesh and still participate in
surface contact. To prescribe time-dependent motion rather than a permanent
pin, use a soft position constraint and update its animation state; see the
animation tutorial and the 92_twisting_bar sample.
Adding ground contact
Starting from the complete program, remove the line that sets
config["contact"]["enable"] = false, then configure and apply a material:
Do this before adding solid to the scene. The tetrahedral mesh already has a
labeled surface; omitting label_surface(solid) is the common reason a solid
does not collide.
Mesh and solver considerations
- Tetrahedra must be non-degenerate and consistently oriented. Inspect input mesh quality before compensating with solver settings.
- The FEM state is per vertex, so duplicating boundary vertices creates disconnected material unless a constraint is added intentionally.
- Material modulus, density, time step, and mesh scale interact. If Newton or PCG convergence degrades, first verify units and element quality, then tune the documented solver tolerances.
linear_system/fem_preconditioner = "mas"enables the MAS preconditioner for every non-empty FEM mesh in the scene. It is a scene-wide performance choice, not a material model, and defaults to"diag".- FEM self-collision is enabled by default by the FEM base constitution. Pair masks and the global contact switch still determine whether candidates are active.
See Scene Configuration for exact defaults and operational domains, and Contact and Collision for activation distances and material stiffness.
Deeper examples and implementation evidence
- animated FEM boundary conditions:
3_periodically_pressed_tetrahedron - larger FEM and MAS setup:
89_mas_bunny - C++ gravity, contact, and pin regressions:
13_fem_3d_gravity.cpp,14_fem_3d_ground_contact.cpp, and15_fem_3d_fixed_point.cpp - public material API:
stable_neo_hookean.h - base FEM attribute initialization:
finite_element_constitution.cpp