Skip to content

DD014.2: Anatomical Mesh Deformation Pipeline

  • Status: Proposed
  • Author: OpenWorm Core Team
  • Date: 2026-02-16
  • Companion to: DD014 (Dynamic Visualization Architecture), DD014.1 (Visual Rendering Specification)
  • Related: DD003 (Body Physics / Sibernetic), DD004 (Mechanical Cell Identity), DD013 (Simulation Stack)

Phase: Phase 4 | Layer: Visualization

TL;DR

The Virtual Worm Blender model contains 688 anatomical meshes (~1.6M vertices) representing every cell in C. elegans. Sibernetic's SPH simulation drives body shape with only ~40K elastic particles. This document specifies how to deform the high-resolution meshes to follow the low-resolution physics — using the body wall mesh as a deformation cage that drives all interior anatomy, with Position-Based Dynamics collision resolution to prevent interpenetration. The result: every cell in the worm bends, stretches, and moves coherently with the simulated body, at 60fps in a WebGPU browser viewer.


Goal & Success Criteria

Criterion Target DD010 Tier
Primary: Visual coherence All 688 meshes deform smoothly with the SPH body shape — no mesh tearing, no vertex explosions, no frozen anatomy against a bending body Visual QC (blocking)
Primary: Spatial containment Interior meshes (intestine, neurons, reproductive) never penetrate the body wall surface Visual QC (blocking)
Primary: Performance Deformation pipeline runs in <4 ms per frame on mid-range GPU (within 60fps budget) Performance (blocking)
Secondary: Inter-mesh non-penetration Adjacent organs (intestine, gonad, muscle quadrants) do not interpenetrate during body bending Visual QC (non-blocking Phase 2)
Tertiary: Anatomical fidelity Anterior-posterior ordering of all organs preserved during deformation — pharynx stays at the head, rectum stays at the tail Visual QC (blocking)

Before: DD014 Viewer Stage 1 (Roadmap Phase 1) uses marching cubes to reconstruct a smooth body surface from SPH particles. The 688 Virtual Worm meshes sit as static, undeformed geometry inside a moving body — completely wrong.

After: All 688 meshes deform in real-time with the simulated body. The cuticle bends, muscles visibly shift within their quadrants, neurons ride along inside the body, and the intestine snakes through the interior — all driven by the SPH physics.


The Resolution Mismatch Problem

Representation Vertices/Particles Purpose
Virtual Worm meshes (all 688) ~1,600,000 vertices Visual rendering (what the user sees)
Sibernetic elastic particles ~40,000 particles Physics simulation (what drives the motion)
Sibernetic liquid particles ~50,000 particles Internal pressure (not directly visible)
Sibernetic boundary particles ~10,000 particles Environment (not visible at organism scale)

The elastic particles outnumber the visual vertices by a ratio of 1:40 — each SPH particle must influence ~40 mesh vertices on average. Direct vertex-to-particle mapping is impossible. We need a technique that interpolates a smooth deformation field from the sparse SPH particles and applies it to the dense mesh vertices.


Technical Approach

Architecture Overview: Three-Stage Pipeline

Stage 1: Body Wall Skinning (SPH → Cage Mesh)
    Input:  ~40K SPH elastic particle positions (per frame)
    Output: Deformed body wall mesh (~50K vertices)
    Method: GPU skinning with particle-derived virtual bones

Stage 2: Interior Deformation (Cage → All Internal Meshes)
    Input:  Deformed body wall mesh (the "cage")
    Output: Deformed positions for all ~1.55M interior vertices
    Method: Cage-based deformation with Mean Value Coordinates

Stage 3: Collision Resolution (Post-Process)
    Input:  All deformed mesh vertex positions
    Output: Adjusted positions with no interpenetration
    Method: Position-Based Dynamics constraint projection

Each stage feeds into the next. All three run as WebGPU compute shaders, chained in a single command buffer per frame.


Stage 1: Body Wall Skinning — SPH Particles to Cage Mesh

Problem: Map ~40K scattered SPH particle positions to ~50K body wall mesh vertices every frame.

Solution: Cluster the SPH particles into virtual bones and use GPU skinning (the same technique used for character animation in games) to deform the cuticle/hypodermis mesh.

Step 1a: Cluster SPH particles into virtual bones (precomputation, done once)

At initialization (t=0), spatially cluster the ~40K elastic particles into ~500 virtual bones using k-means on particle rest positions:

# precompute/cluster_bones.py (runs once at initialization)

import numpy as np
from sklearn.cluster import MiniBatchKMeans

def create_virtual_bones(rest_positions, n_bones=500):
    """
    Cluster ~40K SPH particles into ~500 virtual bones.

    Returns:
        bone_centers: (n_bones, 3) — rest position of each bone
        particle_to_bone: (n_particles,) — which bone each particle belongs to
    """
    kmeans = MiniBatchKMeans(n_clusters=n_bones, random_state=42)
    kmeans.fit(rest_positions)

    return kmeans.cluster_centers_, kmeans.labels_

Each virtual bone represents a local cluster of ~80 SPH particles. Per frame, the bone's position and orientation are computed from its member particles.

Step 1b: Compute bone transforms from SPH particles (per frame)

For each virtual bone, compute a rigid transform (rotation + translation) from its member particles' rest positions to their current positions. This uses the Kabsch algorithm (SVD-based rigid alignment):

# Per frame, on CPU or GPU
for each bone b:
    rest_pts = rest_positions[particles_in_bone[b]]     # (K, 3)
    curr_pts = current_positions[particles_in_bone[b]]   # (K, 3)

    # Centroids
    rest_centroid = mean(rest_pts)
    curr_centroid = mean(curr_pts)

    # Covariance matrix
    H = (rest_pts - rest_centroid).T @ (curr_pts - curr_centroid)

    # SVD → optimal rotation
    U, S, Vt = svd(H)
    R = Vt.T @ U.T
    if det(R) < 0:  # reflection correction
        Vt[-1, :] *= -1
        R = Vt.T @ U.T

    # Translation
    t = curr_centroid - R @ rest_centroid

    bone_transforms[b] = (R, t)  # 3x3 rotation + 3-vector translation

This gives us ~500 rigid transforms per frame — the "skeleton" that drives the visual mesh.

Step 1c: Skin mesh vertices to virtual bones (precomputation + per frame)

Precomputation (done once): For each body wall mesh vertex, find the nearest K=4 virtual bones and compute inverse-distance skinning weights:

# precompute/compute_skin_weights.py

def compute_skin_weights(mesh_vertices, bone_centers, K=4):
    """
    For each mesh vertex, compute weights to its K nearest bones.

    Returns:
        bone_indices: (n_vertices, K) — which bones influence each vertex
        bone_weights: (n_vertices, K) — normalized weights (sum to 1.0)
    """
    from scipy.spatial import cKDTree

    tree = cKDTree(bone_centers)
    distances, indices = tree.query(mesh_vertices, k=K)

    # Inverse distance weights, normalized
    weights = 1.0 / (distances + 1e-8)
    weights /= weights.sum(axis=1, keepdims=True)

    return indices, weights

Per frame (GPU compute shader): Standard Linear Blend Skinning (LBS):

// Stage 1 compute shader: skin body wall vertices to virtual bones
@group(0) @binding(0) var<storage, read> rest_positions: array<f32>;
@group(0) @binding(1) var<storage, read> bone_transforms: array<f32>; // 500 bones × 12 floats (3x3 R + 3 t)
@group(0) @binding(2) var<storage, read> bone_indices: array<u32>;     // n_verts × 4
@group(0) @binding(3) var<storage, read> bone_weights: array<f32>;     // n_verts × 4
@group(0) @binding(4) var<storage, read_write> deformed_positions: array<f32>;

@compute @workgroup_size(256)
fn skin_body_wall(@builtin(global_invocation_id) id: vec3u) {
    let vi = id.x;
    if (vi >= num_vertices) { return; }

    let rest_x = rest_positions[vi * 3 + 0];
    let rest_y = rest_positions[vi * 3 + 1];
    let rest_z = rest_positions[vi * 3 + 2];

    var pos = vec3f(0.0);

    for (var i = 0u; i < 4u; i++) {
        let bi = bone_indices[vi * 4 + i];
        let w  = bone_weights[vi * 4 + i];

        // Read 3x3 rotation + translation for this bone
        let base = bi * 12;
        let R = mat3x3f(
            vec3f(bone_transforms[base+0], bone_transforms[base+1], bone_transforms[base+2]),
            vec3f(bone_transforms[base+3], bone_transforms[base+4], bone_transforms[base+5]),
            vec3f(bone_transforms[base+6], bone_transforms[base+7], bone_transforms[base+8])
        );
        let t = vec3f(bone_transforms[base+9], bone_transforms[base+10], bone_transforms[base+11]);

        pos += w * (R * vec3f(rest_x, rest_y, rest_z) + t);
    }

    deformed_positions[vi * 3 + 0] = pos.x;
    deformed_positions[vi * 3 + 1] = pos.y;
    deformed_positions[vi * 3 + 2] = pos.z;
}

Why LBS works for a worm: Linear Blend Skinning produces artifacts at sharply rotating joints (the "candy wrapper" effect). But C. elegans bends smoothly along its length — there are no discrete joints. The smooth bending of the worm body is exactly the case where LBS performs best. If artifacts are observed at extreme curvature, Dual Quaternion Skinning (DQS) is a drop-in replacement that preserves volume better, at minimal additional cost.


Stage 2: Interior Deformation — Cage-Based Deformation with Mean Value Coordinates

Problem: After Stage 1, the body wall mesh (cuticle + hypodermis) is correctly deformed. But the ~1.55M interior vertices (neurons, muscles, intestine, pharynx, reproductive system) are still in their rest positions.

Solution: Use the body wall mesh as a deformation cage. The cage is a closed surface that encloses all interior anatomy. When the cage deforms, everything inside follows smoothly.

What are cage coordinates?

For every interior vertex, we precompute a set of generalized barycentric coordinates — weights that express its position as a weighted combination of cage (body wall) vertices:

interior_position = Σ_i (w_i × cage_vertex_i)

If these weights are computed correctly, they have two critical properties:

  1. Interpolation: When the cage vertex positions change, the interior point moves smoothly
  2. Containment: If weights are non-negative, the interior point stays inside the cage

Mean Value Coordinates (MVC)

MVC (Floater 2003, Ju et al. 2005) are the simplest generalized barycentric coordinates for arbitrary closed triangle meshes. For an interior point p and cage vertex v_i:

w_i(p) = [ Σ_f∈faces_incident(v_i)  α_i(f, p) ] / Σ_j [ Σ_f∈faces_incident(v_j)  α_j(f, p) ]

Where α values are derived from the solid angles subtended by cage faces as seen from p. The full formula involves tangent half-angle computations over each cage triangle.

Key properties for our use case:

  • Defined for any closed triangle mesh (our body wall is a closed surface)
  • Smooth and continuous everywhere inside the cage
  • Weights sum to 1.0
  • Mostly non-negative for convex-ish cages (the worm body is roughly convex in cross-section)
  • Computational complexity: O(V_interior × F_cage) for precomputation, O(V_interior × K_sparse) per frame

Sparsification for performance

The full weight matrix is (1.55M interior vertices × ~100K cage faces) — too large. But MVC weights fall off with distance, so we sparsify: for each interior vertex, keep only the K=30 largest weights (nearest cage vertices), set the rest to zero, and renormalize.

# precompute/compute_cage_coordinates.py

from pymvc import mean_value_coordinates  # hypothetical library

def compute_sparse_mvc(interior_vertices, cage_vertices, cage_faces, K=30):
    """
    Compute sparse Mean Value Coordinates for all interior vertices.

    Returns:
        cage_indices: (n_interior, K) — which cage vertices influence each interior vertex
        cage_weights: (n_interior, K) — normalized sparse weights
    """
    n_interior = len(interior_vertices)
    all_indices = np.zeros((n_interior, K), dtype=np.int32)
    all_weights = np.zeros((n_interior, K), dtype=np.float32)

    for i, p in enumerate(interior_vertices):
        # Compute full MVC weights (slow, but done once)
        weights = mean_value_coordinates(p, cage_vertices, cage_faces)

        # Keep top K weights
        top_k = np.argsort(np.abs(weights))[-K:]
        sparse_w = weights[top_k]
        sparse_w /= sparse_w.sum()  # renormalize

        all_indices[i] = top_k
        all_weights[i] = sparse_w

    return all_indices, all_weights

Per-frame GPU compute shader

// Stage 2 compute shader: deform interior vertices using cage coordinates
@group(0) @binding(0) var<storage, read> cage_positions: array<f32>;    // From Stage 1 output
@group(0) @binding(1) var<storage, read> cage_indices: array<u32>;      // (n_interior, K)
@group(0) @binding(2) var<storage, read> cage_weights: array<f32>;      // (n_interior, K)
@group(0) @binding(3) var<storage, read_write> interior_positions: array<f32>;

const K: u32 = 30;

@compute @workgroup_size(256)
fn deform_interior(@builtin(global_invocation_id) id: vec3u) {
    let vi = id.x;
    if (vi >= num_interior_vertices) { return; }

    var pos = vec3f(0.0);

    for (var i = 0u; i < K; i++) {
        let ci = cage_indices[vi * K + i];
        let w  = cage_weights[vi * K + i];

        pos += w * vec3f(
            cage_positions[ci * 3 + 0],
            cage_positions[ci * 3 + 1],
            cage_positions[ci * 3 + 2]
        );
    }

    interior_positions[vi * 3 + 0] = pos.x;
    interior_positions[vi * 3 + 1] = pos.y;
    interior_positions[vi * 3 + 2] = pos.z;
}

The body wall mesh as cage: practical considerations

The Virtual Worm cuticle + hypodermis meshes form the outermost surface of the worm body. Together they define a closed volume. However:

  1. The cage must be a single closed mesh. The Virtual Worm splits the outer surface into multiple meshes (cuticle, hyp1-hyp11, seam cells). These must be merged into a single watertight cage mesh at initialization. Use a boolean union or simply stitch shared edges.

  2. Cage resolution vs. quality. The full cuticle mesh has ~50K vertices. MVC precomputation with a 50K-vertex cage on 1.55M interior points is expensive but feasible as a one-time cost (~10-30 minutes). If too slow, decimate the cage to ~5K-10K vertices (sufficient for smooth deformation of a worm body) and compute MVC against the simplified cage.

  3. Open ends. The worm has open ends (mouth, anus). The cage must be capped with simple polygons at both ends to form a closed volume. These caps can be flat discs positioned at the anterior and posterior extremes of the body wall mesh.


Stage 3: Collision Resolution — Position-Based Dynamics

Problem: Cage-based deformation produces smooth, coherent motion but does not prevent inter-mesh penetration. During extreme bending, the intestine might clip through a muscle layer, or two gonad arms might overlap.

Solution: After Stages 1+2, run a Position-Based Dynamics (PBD) constraint pass to project overlapping vertices apart.

Collision pairs

Not all 688 meshes need collision testing against each other. Define a collision pair table based on anatomical adjacency:

Mesh A Mesh B Why
Body wall (cuticle) All interior meshes Nothing should poke through the skin
Intestine Body wall muscles Adjacent structures, often in contact
Intestine Gonad arms Wrap around each other in the body cavity
Pharynx Neurons (anterior) Tightly packed in the head
Gonad arm (anterior) Gonad arm (posterior) U-shaped gonad folds back on itself

This reduces collision detection from O(688²) = 473,344 pairs to ~20-30 relevant pairs.

PBD algorithm

for iteration in range(N_ITERATIONS):  # typically 5-10
    for each collision pair (mesh_A, mesh_B):

        # Broad phase: BVH overlap test (AABB tree)
        overlapping_triangles = bvh_overlap(mesh_A.bvh, mesh_B.bvh)

        # Narrow phase: vertex-triangle distance test
        for (vertex, triangle) in overlapping_triangles:
            distance = signed_distance(vertex, triangle)
            if distance < 0:  # penetration
                normal = triangle.normal
                correction = -distance * normal * 0.5  # split correction between both meshes
                vertex.position += correction
                # Also nudge triangle vertices in opposite direction
                for tv in triangle.vertices:
                    tv.position -= correction / 3.0

    # Optional: volume preservation constraint
    for each mesh with volume tracking:
        current_volume = compute_volume(mesh)
        if current_volume < 0.9 * rest_volume:
            scale_outward(mesh, target=rest_volume)

GPU implementation

PBD parallelizes well with Jacobi iteration (each constraint resolved independently, averaged after each iteration). Recent work demonstrated 640K nodes at 60fps in WebGPU for cloth simulation with triangle-triangle collision. Our ~50K total vertices for collision-active meshes are well within budget.

// Stage 3 compute shader: PBD collision resolution
// Runs after Stages 1+2 have written deformed positions

@compute @workgroup_size(256)
fn resolve_collisions(@builtin(global_invocation_id) id: vec3u) {
    let vi = id.x;
    if (vi >= num_collision_vertices) { return; }

    var correction = vec3f(0.0);
    var num_corrections = 0u;

    // For each potential collision triangle (from BVH broad phase, precomputed)
    for (var i = 0u; i < max_collision_candidates; i++) {
        let tri_idx = collision_candidates[vi * max_collision_candidates + i];
        if (tri_idx == 0xFFFFFFFF) { break; }  // sentinel: no more candidates

        let d = signed_distance_to_triangle(positions[vi], tri_idx);
        if (d < 0.0) {
            let n = triangle_normal(tri_idx);
            correction += (-d + EPSILON) * n;
            num_corrections += 1u;
        }
    }

    if (num_corrections > 0u) {
        positions[vi * 3 + 0] += correction.x / f32(num_corrections);
        positions[vi * 3 + 1] += correction.y / f32(num_corrections);
        positions[vi * 3 + 2] += correction.z / f32(num_corrections);
    }
}

The Spine Shortcut (Optimization for Phase 1)

The Virtual Worm Blender file contains a bezier spine curve running along the body's central axis. The Blender2NeuroML repo already has blenderStraighten.py that uses it. This offers a fast Phase 1 approximation before the full cage pipeline is ready:

Spine-based deformation (simpler, faster, less accurate):

  1. Fit a deformable spine curve (cubic B-spline, ~20 control points) to the SPH particle centerline each frame
  2. Each mesh vertex is parameterized at initialization by (s, r, θ) — distance along spine, distance from spine, angle around spine
  3. Per frame: evaluate the new spine at parameter s, orient the local frame by the spine tangent/normal, and place the vertex at offset (r, θ) in the local frame
# Per vertex (precomputed):
s = project_onto_spine(vertex_rest_position, spine_rest)  # arc length parameter
r = distance_from_spine(vertex_rest_position, spine_rest) # radial distance
theta = angle_around_spine(vertex_rest_position, spine_rest) # angular position

# Per frame:
new_spine_point = evaluate_spine(s, current_spine_control_points)
T, N, B = frenet_frame(s, current_spine_control_points)  # tangent, normal, binormal
deformed_position = new_spine_point + r * (cos(theta) * N + sin(theta) * B)

Advantages: Extremely fast (just a spline evaluation per vertex), preserves anterior-posterior ordering perfectly, interior structures ride along naturally.

Disadvantages: Cannot capture cross-sectional deformation (e.g., muscles bulging during contraction), treats the worm as a rigid cross-section sliding along a flexible centerline. The body cross-section shape is assumed constant.

Recommendation: Use spine-based deformation for Phase 1 (gets meshes moving immediately), then upgrade to the full cage pipeline for Phase 2 (captures cross-sectional deformation and individual muscle bulging).


Deliverables

Artifact Path (relative to repo) Format Description
Virtual bone cluster data openworm/Worm3DViewerdata/bone_clusters.npz NumPy archive 500 bone centers, particle-to-bone assignments
Body wall skin weights data/body_wall_skin_weights.npz NumPy archive (n_cuticle_verts, 4) indices + weights
Watertight cage mesh data/cage_mesh.obj OBJ Merged + capped cuticle/hypodermis mesh
MVC cage coordinates (sparse) data/cage_coordinates.npz NumPy archive (n_interior_verts, 30) indices + weights
Spine parameters data/spine_parameters.npz NumPy archive Per-vertex (s, r, theta) for all 688 meshes
Precomputation script scripts/precompute_deformation.py Python Generates all above from Virtual Worm meshes + SPH rest positions
Stage 1 compute shader shaders/skin_body_wall.wgsl WGSL GPU skinning of body wall
Stage 2 compute shader shaders/deform_interior.wgsl WGSL Cage-based interior deformation
Stage 3 compute shader shaders/resolve_collisions.wgsl WGSL PBD collision resolution
Spine deformation shader shaders/spine_deform.wgsl WGSL Phase 1 spine-based shortcut
Collision pair config data/collision_pairs.json JSON Which mesh pairs need collision testing

Repository & Issues

Item Value
Repository openworm/Worm3DViewer
Issue label dd014b
Milestone Anatomical Mesh Deformation
Branch convention dd014b/description (e.g., dd014b/cage-deformation, dd014b/spine-shortcut)
Example PR title DD014.2: Implement spine-based deformation for Phase 1 viewer
Source meshes openworm/Blender2NeuroMLsrc/Data/Virtual_Worm_February_2012.blend

How to Build & Test

Prerequisites

  • Python 3.10+, numpy, scipy, scikit-learn, trimesh, pyvista
  • Blender 3.6+ (for mesh export from Virtual Worm .blend file)
  • WebGPU-capable browser (Chrome 113+, Firefox 121+)

Getting Started (Environment Setup)

There are two paths: Docker (recommended for integration testing) and native Python + Blender (for mesh pipeline development).

Clone the required repositories:

git clone https://github.com/openworm/Blender2NeuroML.git   # Virtual Worm source meshes
git clone https://github.com/openworm/OpenWorm.git           # for docker compose integration

Path A — Docker (integration testing):

cd OpenWorm
docker compose build viewer     # builds viewer stage with mesh deformation deps
docker compose up viewer        # launches viewer with deformed meshes at localhost:8501

This path uses the pre-built deformation pipeline inside the Docker viewer service. See DD013 for full Docker environment setup.

Path B — Native Python + Blender (pipeline development):

# Install Python mesh processing dependencies
pip install numpy scipy scikit-learn trimesh pyvista

# Install STL/OBJ mesh I/O
pip install numpy-stl

# Verify Blender is available (needed for mesh export from .blend file)
blender --version
# Expected: Blender 3.6+ (4.x recommended)

# Blender headless mesh export (no GUI required)
blender --background --python scripts/export_virtual_worm_meshes.py \
    -- Blender2NeuroML/src/Data/Virtual_Worm_February_2012.blend output/meshes/

The Virtual Worm Blender file (Virtual_Worm_February_2012.blend) contains 688 anatomical meshes (~1.6M vertices). The export script converts these to individual OBJ files for the deformation pipeline.

Blender headless Docker (alternative):

If you do not have Blender installed natively, you can use a headless Blender container:

docker run --rm -v $(pwd):/work -w /work \
    nytimes/blender:4.0-cpu \
    blender --background --python scripts/export_virtual_worm_meshes.py \
    -- /work/Blender2NeuroML/src/Data/Virtual_Worm_February_2012.blend /work/output/meshes/

Step-by-step

# Step 1: Export Virtual Worm meshes from Blender to individual OBJ files
blender --background --python scripts/export_virtual_worm_meshes.py \
    -- src/Data/Virtual_Worm_February_2012.blend output/meshes/
# [TO BE CREATED] — GitHub issue: openworm/Worm3DViewer#TBD
# Expected: 688 OBJ files in output/meshes/

# Step 2: Generate watertight cage mesh from cuticle + hypodermis
python scripts/build_cage_mesh.py \
    --meshes output/meshes/cuticle.obj output/meshes/hyp*.obj \
    --output data/cage_mesh.obj
# [TO BE CREATED] — GitHub issue: openworm/Worm3DViewer#TBD
# Expected: single closed OBJ mesh

# Step 3: Run full precomputation (bones, weights, cage coords, spine params)
python scripts/precompute_deformation.py \
    --meshes output/meshes/ \
    --cage data/cage_mesh.obj \
    --sph_rest_positions /path/to/sibernetic/worm_rest_positions.dat \
    --output data/
# [TO BE CREATED] — GitHub issue: openworm/Worm3DViewer#TBD
# Expected: bone_clusters.npz, body_wall_skin_weights.npz,
#           cage_coordinates.npz, spine_parameters.npz

# Step 4: Run deformation unit test (known displacement → expected output)
python scripts/test_deformation.py \
    --test_case uniform_bend_90deg \
    --precomputed data/
# [TO BE CREATED] — GitHub issue: openworm/Worm3DViewer#TBD
# Expected: all interior vertices remain inside cage, max penetration < 1 µm

# Step 5: Run performance benchmark
python scripts/benchmark_deformation.py \
    --precomputed data/ \
    --n_frames 100
# [TO BE CREATED] — GitHub issue: openworm/Worm3DViewer#TBD
# Expected: per-frame time < 4 ms (CPU reference), < 1 ms (GPU target)

# Step 6: Visual integration test
docker compose up viewer
# Navigate to localhost:8501, load a Sibernetic replay
# Verify: all meshes deform with body, no penetration visible

Scripts that don't exist yet

Script Status Tracking
scripts/export_virtual_worm_meshes.py [TO BE CREATED] openworm/Worm3DViewer#TBD
scripts/build_cage_mesh.py [TO BE CREATED] openworm/Worm3DViewer#TBD
scripts/precompute_deformation.py [TO BE CREATED] openworm/Worm3DViewer#TBD
scripts/test_deformation.py [TO BE CREATED] openworm/Worm3DViewer#TBD
scripts/benchmark_deformation.py [TO BE CREATED] openworm/Worm3DViewer#TBD
shaders/skin_body_wall.wgsl [TO BE CREATED] openworm/Worm3DViewer#TBD
shaders/deform_interior.wgsl [TO BE CREATED] openworm/Worm3DViewer#TBD
shaders/resolve_collisions.wgsl [TO BE CREATED] openworm/Worm3DViewer#TBD
shaders/spine_deform.wgsl [TO BE CREATED] openworm/Worm3DViewer#TBD

How to Visualize

This DD is itself a visualization pipeline. Its output IS the deformed meshes rendered in the DD014 viewer.

Viewer Feature Specification
Default view Deformed Virtual Worm meshes replace marching-cubes surface at Phase 2+
Debug mode: bones Toggle to show virtual bone positions as small spheres + lines to their SPH particles
Debug mode: cage Toggle to show the cage mesh as a wireframe overlaid on the deformed body
Debug mode: collision Toggle to highlight vertices that were corrected by PBD (red = penetration detected)
A/B comparison Side-by-side: marching cubes surface (Phase 1) vs. deformed Virtual Worm meshes (Phase 2)

Alternatives Considered

1. Free-Form Deformation (FFD) Lattice

Description: Embed the entire worm in a regular 3D grid of control points (e.g., 20 × 4 × 4). Map control points to SPH particles. Deforming the lattice deforms all embedded meshes via trivariate Bernstein polynomial evaluation.

Rejected because:

  • A regular lattice is a poor fit for the worm's elongated, curved anatomy — most lattice cells would be empty
  • Lattice resolution limits the spatial detail of deformation (cannot capture localized muscle bulging without very fine lattice)
  • Requires manual lattice design or adaptive lattice generation

When to reconsider: If the cage mesh approach proves too complex or if cross-sectional deformation fidelity is less important than speed.

2. Radial Basis Function (RBF) Interpolation

Description: Use SPH particle displacements as scattered control points for a smooth RBF deformation field. Evaluate the field at every mesh vertex.

Rejected because:

  • Requires solving a linear system per frame (N × N where N = number of control points). With ~40K SPH particles, this is a 40K × 40K system — infeasible per frame even with precomputed factorization
  • Subsampling to ~500 control points is possible but then RBF offers no advantage over the virtual-bone skinning approach (Stage 1)
  • Does not inherently provide containment guarantees (interior vertices can escape the body)

When to reconsider: If the skinning + cage approach produces visible artifacts and a globally smooth deformation field is needed.

3. Harmonic Coordinates Instead of Mean Value Coordinates

Description: Use Harmonic Coordinates (DeRose et al. 2007, Pixar) instead of MVC for the cage deformation. HC are strictly non-negative inside any cage, producing more intuitive deformations.

Deferred because:

  • HC require solving a 3D Laplace equation at precomputation time (voxelize the interior, solve ∇²w_i = 0 with boundary conditions). This is more expensive than MVC's closed-form evaluation
  • MVC are sufficient for the smooth, convex-ish cross-section of the worm body. The cases where MVC produce negative weights (highly concave cages) do not apply here
  • HC can be adopted later as a drop-in replacement if MVC artifacts are observed

When to switch: If MVC produces visible artifacts (interior vertices moving in wrong directions during extreme bending near the head or tail, where the body tapers).

4. Green Coordinates (Shape-Preserving)

Description: Green Coordinates (Lipman et al. 2008) preserve local shape (conformal deformation) by incorporating cage face normals, not just vertex positions.

Deferred because:

  • Highest computational cost per vertex (requires face normal contributions in addition to vertex positions)
  • Shape preservation is less critical for anatomical visualization than for character animation
  • Gains over MVC are marginal for smooth bending deformations

When to try: If implementing a close-up view of muscle striations or fine anatomical detail that requires preservation of local surface structure during deformation.

5. Direct SPH-to-Vertex Mapping (No Cage)

Description: Assign each mesh vertex to its nearest SPH particle(s) and interpolate directly.

Rejected because:

  • 40K particles driving 1.6M vertices means each particle drives ~40 vertices. The mapping is extremely sparse — many mesh vertices would be far from any particle
  • No containment guarantee — interior meshes can easily escape the body surface
  • Produces visible artifacts: mesh vertices "snap to" discrete particle positions rather than deforming smoothly
  • Does not preserve inter-mesh spatial relationships (each mesh deforms independently)

6. Re-simulate All Meshes as SPH Particles

Description: Convert every Virtual Worm mesh vertex into an SPH particle and simulate the complete anatomy at full resolution.

Rejected because:

  • 1.6M particles would be ~16× the current particle count, increasing simulation time by ~16² = 256× (SPH is O(N²) with neighbor search)
  • Sibernetic is a physics engine, not a rendering engine. The detailed mesh vertices exist only for visual quality, not for physics
  • Completely infeasible for real-time or even practical offline simulation

Quality Criteria

What Defines a Valid Mesh Deformation Implementation?

  1. No vertex explosions. No mesh vertex may move more than 100 µm from its cage-predicted position in a single frame. Any vertex exceeding this threshold is clamped and flagged.

  2. Cage containment. After Stage 2 + Stage 3, ≥99.5% of interior vertices must lie inside the cage mesh (signed distance ≥ 0). The remaining 0.5% tolerance accounts for vertices near the cage surface (within 1 particle spacing).

  3. Anterior-posterior preservation. The anterior-posterior ordering of all anatomical structures must be preserved. Define: for any two meshes A, B where A is anatomically anterior to B, the centroid of deformed A must be anterior to the centroid of deformed B. Test with the 20 intestinal cells (int1 through int9, strict ordering).

  4. Frame coherence. No mesh vertex may move more than 10 µm between consecutive frames (at standard 60fps playback). Sudden jumps indicate numerical instability.

  5. Performance. The complete 3-stage pipeline (skinning + cage + PBD) must execute in <4 ms per frame on a mid-range discrete GPU (e.g., NVIDIA GTX 1660, Apple M1). Spine-based shortcut must execute in <1 ms.

  6. Backward compatibility. When visualization.mesh_deformation: false in openworm.yml, the viewer falls back to marching-cubes surface reconstruction (DD014 Viewer Stage 1). No regression in existing functionality.

Validation Procedure

# 1. Containment test (static)
python scripts/test_deformation.py --test_case no_deformation
# Expected: 100% vertices inside cage (rest positions = cage rest)

# 2. Containment test (bent body)
python scripts/test_deformation.py --test_case uniform_bend_90deg
# Expected: ≥99.5% vertices inside cage

# 3. Ordering test
python scripts/test_deformation.py --test_case s_curve_crawl
# Expected: intestinal cell centroids maintain anterior→posterior order

# 4. Performance benchmark
python scripts/benchmark_deformation.py --n_frames 1000
# Expected: mean per-frame time < 4 ms, max < 8 ms

# 5. Visual inspection (requires human)
# Load a 10-second Sibernetic replay in the viewer
# Verify: no visible mesh tearing, no organs poking through skin,
#         smooth motion throughout body bending

Boundaries (Explicitly Out of Scope)

What This Design Document Does NOT Cover:

  1. Muscle contraction visualization. Visible muscle bulging/shortening during contraction is a Phase 3 enhancement requiring per-muscle deformation offsets driven by DD002 activation signals. DD014.2 handles only the gross body-shape deformation.

  2. Pharynx pumping deformation. The pharynx pumps rhythmically (DD007), requiring a local deformation that compresses the lumen. This needs a dedicated pharyngeal deformation model (separate from the body cage), deferred to Phase 2.

  3. Growth and developmental changes. Body size and shape change during larval development (L1-L4 → adult). DD014.2 assumes a fixed adult body. Developmental morphing is Phase 6 work.

  4. Real-time SPH simulation. DD014.2 consumes pre-recorded SPH output. It does not run Sibernetic in real-time.

  5. Mesh generation or repair. DD014.2 assumes the Virtual Worm meshes are valid and watertight. If mesh defects exist (holes, non-manifold edges, self-intersections), they must be repaired upstream.

  6. NeuroML morphology deformation. Neuron morphologies from NeuroML (soma spheres + dendrite/axon tubes) are separate from Virtual Worm meshes. Their deformation follows the same cage pipeline but uses the c302-derived geometry, not Virtual Worm OBJ data. Integration is future work.

  7. Color, lighting, and rendering. See DD014.1. DD014.2 is purely about geometry deformation.


Context & Background

The Virtual Worm

The Virtual Worm was created by Dr. Christian Grove and Dr. Paul Sternberg at Caltech/WormBase. It is the most complete 3D anatomical model of C. elegans, containing meshes for every identified cell in the adult hermaphrodite. Two versions exist in openworm/Blender2NeuroML:

  • Virtual_Worm_March_2011.blend (~18 MB)
  • Virtual_Worm_February_2012.blend (~11 MB, used by Worm3DViewer)

The model organizes cells into 4 layers: Cuticle (15 meshes), Neurons (362 meshes), Muscles (113 meshes), Organs (198 meshes), totaling 688 mesh objects across 37 material categories.

The Decade-Long Gap

This problem was identified in 2015. Geppetto issue #268 ("Need exact definition of muscle contraction movement") documented the discussion between @gitmado and @a-palyanov about how to skin the Virtual Worm meshes to the Sibernetic simulation. The proposed "imaginary skeleton" approach was never implemented. The blenderStraighten.py script in Blender2NeuroML demonstrates spine-based coordinate transformation but is a one-way offline tool, not a real-time deformation pipeline.

For 10 years, the Virtual Worm meshes and the Sibernetic simulation have existed as parallel representations with no bridge between them. DD014.2 closes this gap.

State of the Art (2026)

The techniques specified here — GPU skinning, cage-based deformation, PBD collision — are production-proven in game engines and film VFX. Key references:

  • WebGPU skinning is built into Three.js r172+ (SkinnedMesh with WebGPU backend)
  • A 2025 paper demonstrated real-time PBD cloth simulation at 640K nodes / 60fps in WebGPU
  • Cage-based deformation has been production-standard at Pixar since 2007 (Harmonic Coordinates)
  • The *Cages system (Le & Deng 2013) specifically addresses nested cage hierarchies — exactly our use case of body wall enclosing organs enclosing sub-structures

The tools exist. The anatomical data exists. DD014.2 specifies how to connect them.


Implementation Roadmap

Phase 1: Spine-Based Deformation (Weeks 1-4)

Get meshes moving immediately with the simpler spine approach.

Task Effort Dependency
Export Virtual Worm meshes to individual OBJ files 4 hrs Blender access
Extract spine curve from Blender file 2 hrs blenderExportSpine.py exists
Compute per-vertex spine parameters (s, r, θ) 8 hrs Mesh export
Implement spine-based deformation in WGSL compute shader 16 hrs Spine params
Fit B-spline to SPH particle centerline per frame 8 hrs Sibernetic output
Integrate into Worm3DViewer 8 hrs Trame/Three.js viewer
Visual validation: load replay, verify meshes follow body 4 hrs Integration

Deliverable: All 688 meshes bend and translate with the simulated body shape. Cross-sectional deformation is approximate (rigid cross-section assumption).

Phase 2: Full Cage Pipeline (Weeks 5-12)

Replace spine shortcut with the three-stage pipeline.

Task Effort Dependency
Build watertight cage mesh from cuticle/hypodermis 8 hrs Mesh export
Cluster SPH particles into virtual bones 4 hrs SPH rest positions
Compute body wall skin weights (vertex → 4 bones) 4 hrs Bone clusters
Implement Stage 1 compute shader (LBS skinning) 8 hrs Weights
Compute sparse MVC cage coordinates for all interior vertices 16 hrs Cage mesh
Implement Stage 2 compute shader (cage deformation) 8 hrs MVC coords
Define collision pair table 2 hrs Anatomical knowledge
Implement Stage 3 compute shader (PBD collision) 16 hrs Collision pairs
Validation: containment, ordering, performance 8 hrs Full pipeline
Replace spine shortcut in viewer 4 hrs Full pipeline working

Deliverable: Full three-stage deformation pipeline running in WebGPU. Interior anatomy deforms with cross-sectional accuracy. No interpenetration.

Phase 3: Refinements (Weeks 13-16)

Task Effort Dependency
Per-muscle deformation offsets (visible contraction bulging) 16 hrs DD002 activation data
Pharyngeal pumping local deformation 8 hrs DD007 pumping state
LOD for deformation (skip PBD at organism scale) 4 hrs Performance profiling
Dual Quaternion Skinning upgrade (if LBS artifacts visible) 8 hrs Phase 2 working

Performance Budget

Stage Operation Vertices GPU Time (estimated)
Stage 1 Skin body wall (LBS, 4 bones/vert) ~50K 0.2 ms
Stage 2 Cage deform interior (30 weights/vert) ~1.55M 1.5 ms
Stage 3 PBD collision (10 iterations, sparse) ~50K active 1.5 ms
Total ~3.2 ms
Spine shortcut (Phase 1) Spline eval per vertex ~1.6M 0.8 ms

Budget is 16.6 ms per frame for 60fps. Deformation uses ~20% of the frame budget, leaving ~13 ms for rendering (well within Three.js capabilities for this mesh complexity).


References

  1. Floater MS (2003). "Mean value coordinates." Computer Aided Geometric Design 20:19-27. Original MVC formulation.

  2. Ju T, Schaefer S, Warren J (2005). "Mean value coordinates for closed triangular meshes." ACM SIGGRAPH 2005. Extension of MVC to 3D closed meshes.

  3. DeRose T, Meyer M, et al. (2007). "Harmonic Coordinates for Character Articulation." ACM SIGGRAPH 2007. Harmonic coordinates (alternative to MVC with non-negativity guarantee).

  4. Lipman Y, Levin D, Cohen-Or D (2008). "Green Coordinates." ACM Trans. Graphics 27(3). Shape-preserving cage deformation.

  5. Le BH, Deng Z (2013). "Cages: A Multi-level, Multi-cage System for Mesh Deformation." ACM Trans. Graphics 32(6). Nested cage hierarchies — relevant for body wall → organs → sub-structures.*

  6. Kavan L, Collins S, Zara J, O'Sullivan C (2007). "Skinning with Dual Quaternions." I3D 2007. DQS — volume-preserving alternative to LBS.

  7. WebGPU Cloth Simulation (2025). Real-time PBD cloth simulation maintaining 60fps with 640K nodes in WebGPU. Performance benchmark for PBD in browser.

  8. Toji (2025). "Using WebGPU Compute Shaders with Vertex Data." Best practices for compute-based vertex deformation in WebGPU.

  9. Three.js WebGPU Skinning. github.com/mrdoob/three.js/blob/dev/examples/webgpu_skinning.html — Built-in GPU skinning example.

  10. Stroter et al. (2024). "A Survey on Cage-based Deformation of 3D Models." Computer Graphics Forum. Comprehensive review of cage deformation techniques.

  11. Virtual Worm Project. Grove & Sternberg, Caltech/WormBase. openworm/Blender2NeuroML — Source Blender files and conversion scripts.

  12. Geppetto Issue #268. openworm/org.geppetto/issues/268 — Original 2015 discussion on muscle contraction mesh deformation.


Integration Contract

Inputs / Outputs

Inputs (What This Subsystem Consumes)

Input Source DD Variable Format Units
SPH elastic particle positions (per frame) DD003 (Sibernetic) Per-particle (x, y, z) OME-Zarr: body/positions/ or direct from Sibernetic state µm
SPH elastic particle rest positions DD003 (Sibernetic) Per-particle (x, y, z) at t=0 Snapshot µm
Virtual Worm mesh files (static) Blender2NeuroML repo OBJ per cell/tissue OBJ mesh files µm
Muscle activation (optional, Phase 3) DD002 Per-muscle [0,1] OME-Zarr: muscle/activation/ dimensionless
Pharynx pumping state (optional, Phase 3) DD007 Per-section [0,1] OME-Zarr: pharynx/pumping_state/ dimensionless

Outputs (What This Subsystem Produces)

Output Consumer DD Variable Format Units
Deformed mesh vertex positions (per frame) DD014 viewer (rendering) Per-vertex (x, y, z) for all 688 meshes GPU buffer (WebGPU GPUBuffer) µm
Deformed mesh normals (per frame) DD014 viewer (lighting) Per-vertex normal vector GPU buffer unit vectors
Collision debug overlay (optional) DD014 viewer (debug mode) Per-vertex boolean (collided/not) GPU buffer boolean

Configuration

visualization:
  mesh_deformation: true             # Enable Virtual Worm mesh deformation
  deformation_method: "cage"         # "spine" (Phase 1) or "cage" (Phase 2+)
  n_virtual_bones: 500               # Number of SPH particle clusters for skinning
  cage_sparsity_k: 30                # Number of cage influences per interior vertex
  pbd_iterations: 8                  # Collision resolution iterations
  pbd_enabled: true                  # Enable collision resolution
Key Default Valid Range Description
visualization.mesh_deformation false true/false Enable Virtual Worm mesh deformation
visualization.deformation_method "spine" "spine", "cage" Deformation pipeline to use
visualization.n_virtual_bones 500 100-2000 Number of SPH particle clusters
visualization.cage_sparsity_k 30 10-100 Cage weight sparsity
visualization.pbd_iterations 8 1-20 PBD collision resolution iterations
visualization.pbd_enabled true true/false Enable PBD collision pass

Coupling Dependencies

I Depend On DD What Breaks If They Change
SPH particle positions DD003 If particle count, indexing, or coordinate frame changes, bone clusters and skin weights must be recomputed
Virtual Worm mesh files External (Blender2NeuroML) If mesh geometry changes, all precomputed weights/coordinates must be regenerated
Muscle activation (Phase 3) DD002 If activation range or muscle ID mapping changes, per-muscle deformation offsets must be updated
OME-Zarr data format DD014 If Zarr schema changes, data loading in the deformation pipeline must update
Depends On Me DD What Breaks If I Change
Visual rendering DD014 / DD014.1 If deformed vertex buffer format changes, the renderer must update its vertex attribute bindings
Cell selection / inspector DD014 If mesh-to-cell-ID mapping changes, click-to-identify must update
Activity coloring DD014.1 Activity color overlay is applied to deformed mesh vertices — deformation must preserve vertex-to-cell mapping (vertex indices must not change)

  • Approved by: Pending
  • Implementation Status: Proposed
  • Next Actions:

  • Export Virtual Worm meshes to individual OBJ files (requires Blender access)

  • Extract and validate spine curve data from Blender file
  • Implement spine-based deformation (Phase 1 — get meshes moving)
  • Build watertight cage mesh from cuticle/hypodermis
  • Implement MVC precomputation and benchmark cage approach
  • Implement three-stage WebGPU compute shader pipeline
  • Visual validation with Sibernetic replay data