Cloth and Thin-Shell Scenes
Cloth is a triangle SimplicialComplex with vertex degrees of freedom. A
membrane constitution supplies in-plane stretch/shear behavior and mass;
DiscreteShellBending is an optional extra constitution for resistance to
folding. Applying bending alone does not create a complete cloth model.
Constitution stack
| Layer | Example API | Purpose |
|---|---|---|
| Triangle topology | trimesh(vertices, triangles) |
Rest surface and vertex state. |
| Collision surface | label_surface(mesh) |
Marks the explicit surface used by contact. |
| Membrane | StrainLimitingBaraffWitkinShell.apply_to(...) |
Stretch, shear, density, thickness, and strain-rate control. |
| Bending, optional | DiscreteShellBending.apply_to(mesh, E, nu) |
Interior-edge bending stiffness computed from membrane thickness. |
| Pins | mesh.vertices()/is_fixed |
Per-vertex fixed state. |
| Contact material | ContactElement.apply_to(mesh) |
Friction/resistance pair identity. |
The membrane must be applied before the formula-based bending overload. The
bending implementation reads the vertex thickness attribute written by the
membrane and evaluates
The raw overload apply_to(mesh, bending_stiffness) defaults to 100 kPa,
but then the supplied value is already the effective edge stiffness and is not
derived from thickness.
Complete pinned-cloth scene
This minimal mesh has two triangles, one shared bending edge, two pinned top vertices, and ground contact. It is intentionally educational rather than a visual demo; production cloth needs enough resolution for the desired folds.
#include <filesystem>
#include <iostream>
#include <uipc/uipc.h>
#include <uipc/constitution/discrete_shell_bending.h>
#include <uipc/constitution/strain_limiting_baraff_witkin.h>
int main()
{
using namespace uipc;
using namespace uipc::constitution;
using namespace uipc::core;
using namespace uipc::geometry;
const std::string workspace = "output/docs/cloth_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"]["d_hat"] = 0.005;
Scene scene{config};
scene.contact_tabular().default_model(0.3, 100.0_MPa);
auto default_contact = scene.contact_tabular().default_element();
vector<Vector3> vertices = {Vector3{-0.5, 1.5, 0.0},
Vector3{0.5, 1.5, 0.0},
Vector3{-0.5, 0.5, 0.0},
Vector3{0.5, 0.5, 0.0}};
vector<Vector3i> triangles = {Vector3i{0, 2, 1}, Vector3i{1, 2, 3}};
auto cloth = trimesh(vertices, triangles);
label_surface(cloth);
auto stretch = ElasticModuli2D::youngs_poisson(50.0_kPa, 0.40);
auto shear = ElasticModuli2D::youngs_poisson(0.5_kPa, 0.40);
StrainLimitingBaraffWitkinShell membrane;
membrane.apply_to(cloth, stretch, shear, 200.0, 0.001, 100.0);
DiscreteShellBending bending;
bending.apply_to(cloth, 50.0_kPa, 0.40);
default_contact.apply_to(cloth);
auto is_fixed = cloth.vertices().find<IndexT>(builtin::is_fixed);
auto fixed = view(*is_fixed);
fixed[0] = 1;
fixed[1] = 1;
scene.objects().create("cloth")->geometries().create(cloth);
auto floor = ground(0.0);
default_contact.apply_to(floor);
scene.objects().create("floor")->geometries().create(floor);
world.init(scene);
if(!world.is_valid())
return 1;
while(world.frame() < 10)
{
world.advance();
world.retrieve();
}
std::cout << "cloth scene reached frame " << world.frame() << '\n';
return world.is_valid() ? 0 : 1;
}
"""Minimal pinned-cloth scene used by the cloth tutorial."""
from pathlib import Path
import numpy as np
import uipc.builtin as builtin
from uipc import Logger, view
from uipc.core import Engine, Scene, World
from uipc.geometry import ground, label_surface, trimesh
from uipc.constitution import (
DiscreteShellBending,
ElasticModuli2D,
StrainLimitingBaraffWitkinShell,
)
from uipc.unit import MPa, kPa
Logger.set_level(Logger.Level.Warn)
workspace = Path("output/docs/cloth")
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"]["d_hat"] = 0.005
scene = Scene(config)
scene.contact_tabular().default_model(0.3, 100.0 * MPa)
default_contact = scene.contact_tabular().default_element()
# Two triangles are enough to expose stretch, shear, one bending edge, pins,
# and contact. Production cloth should use a suitably resolved surface mesh.
vertices = np.array(
[[-0.5, 1.5, 0.0], [0.5, 1.5, 0.0], [-0.5, 0.5, 0.0], [0.5, 0.5, 0.0]]
)
triangles = np.array([[0, 2, 1], [1, 2, 3]])
cloth = trimesh(vertices, triangles)
label_surface(cloth)
stretch = ElasticModuli2D.youngs_poisson(50.0 * kPa, 0.40)
shear = ElasticModuli2D.youngs_poisson(0.5 * kPa, 0.40)
StrainLimitingBaraffWitkinShell().apply_to(
cloth,
stretch_moduli=stretch,
shear_moduli=shear,
mass_density=200.0,
thickness=0.001,
strain_rate=100.0,
)
# Apply membrane first: the formula overload reads its vertex thickness.
DiscreteShellBending().apply_to(cloth, 50.0 * kPa, 0.40)
default_contact.apply_to(cloth)
is_fixed = cloth.vertices().find(builtin.is_fixed)
fixed = view(is_fixed)
fixed[0] = 1
fixed[1] = 1
scene.objects().create("cloth").geometries().create(cloth)
floor = ground(0.0)
default_contact.apply_to(floor)
scene.objects().create("floor").geometries().create(floor)
world.init(scene)
assert world.is_valid()
for _ in range(10):
world.advance()
world.retrieve()
assert world.is_valid()
print(f"cloth scene reached frame {world.frame()}")
The two-modulus overload deliberately separates stretch and shear. Here the
stretch material uses E = 50 kPa, while the shear material uses E = 0.5
kPa; their Poisson ratios are both 0.40. This is useful when a woven sheet
should resist extension much more strongly than in-plane shear.
Parameter meanings and defaults
| Argument | Default in the convenience overload | Practical domain |
|---|---|---|
moduli |
E = 1 MPa, nu = 0.49 for both stretch and shear |
Use E > 0; for ordinary isotropic 2D materials use -1 < nu < 1. |
mass_density |
200 kg/m^3 |
Positive; surface mass scales as density times thickness. |
thickness |
0.001 m |
Positive and expressed in scene length units. Contact also consumes it. |
strain_rate |
100 |
Non-negative amplification coefficient; 0 removes the extra cubic extension penalty, while larger values progressively stiffen over-stretch. |
The C++ and Python bindings expose the same convenience and separated-moduli overloads. The API excludes singular Poisson endpoints but does not centrally validate every physical parameter, so invalid negative values can survive scene assembly and fail later in a less obvious place.
Pinning and animated boundaries
The example marks vertices fixed after the membrane is applied. Fixed state is per vertex, not per triangle. Pinning both endpoints of a top edge is enough for this four-vertex sheet; a real mesh should select a geometrically stable set of support vertices.
For moving grippers or prescribed motion, do not overwrite positions after
world.init(). Add a soft position constraint and update its animation
attributes through the scene's animation/update path. The
91_pinned_cloth
sample is the static-pin baseline.
Contact and self-collision
The FEM base constitution sets self_collision = 1 for cloth as well as
volumetric FEM. It prevents different parts of the same sheet from passing
through each other when global contact is enabled. The contact table still
controls material pairs, and the cloth still needs label_surface.
For cloth, inspect these values together:
- physical
thicknesson vertices; contact/d_hatorcontact/d_hat_relative;- triangle size and aspect ratio;
contact/eps_velocitywhen friction is enabled; and- contact resistance, which is independent of membrane and bending stiffness.
d_hat is a barrier activation distance, not a replacement for thickness.
Excessive activation distance on a finely discretized sheet can create many
more candidates and make the solve unnecessarily stiff.
Deeper examples and implementation evidence
- cloth material and bending:
11_bunny_cloth - multiple self-contacting sheets:
34_cloth_stack - pinned source-backed scene:
91_pinned_cloth - C++ pin, bending, and MAS regressions:
19_shell_fixed_point.cpp,33_discrete_shell_bending.cpp, and60_fem_mas_cloth.cpp - public membrane and bending APIs:
strain_limiting_baraff_witkin.handdiscrete_shell_bending.h