As-Rigid-As-Possible
Surface Modeling
An interactive surface editing tool that preserves local shape detail by optimizing per-vertex rigid transformations, implemented from scratch in C++ with Eigen.
The problem
When deforming a 3D surface, users expect the mesh to behave like a physical material, bending and twisting smoothly without shearing or distorting local features. Naive Laplacian-based editing fails at this: large translations cause detail loss and unnatural stretching because there is no rotation field to propagate. This project implements the ARAP (As-Rigid-As-Possible) method of Sorkine and Alexa (SGP 2007) to fix exactly that.
But how exactly do we preserve local features? By making every small patch of the surface move as rigidly as it can. To make that precise, the mesh is covered in overlapping cells: the cell \(C_i\) for a vertex \(\mathbf{p}_i\) is its one-ring neighborhood: \(\mathbf{p}_i\), its directly connected neighbors \(N(i)\), and the edges \(\mathbf{e}_{ij} = \mathbf{p}_i - \mathbf{p}_j\) between them.
When the user deforms the mesh, vertices are displaced to new positions \(\mathbf{p}'_i\), \(\mathbf{p}'_j\), and each edge becomes \(\mathbf{e}'_{ij}\). Let \(R_i\) be a rotation carrying the cell's rest edges onto its deformed ones. If the cell moved perfectly rigidly, a single rotation would explain every edge simultaneously:
Real edits bend and stretch, so no such rotation exists. The equation can't hold for every neighbor at once. Instead, we ask for the rotation that comes closest: the one minimizing the cell's rigidity energy,
This is the failure of rigidity for one cell: each term measures how far a deformed edge is from being a pure rotation of its rest edge. Summing it over every vertex gives the total energy the method minimizes. Deformations that preserve local features are exactly the ones that keep every cell's term small:
Solving it: alternate between rotations and positions
The unknowns come in pairs: the positions \(\mathbf{p}'\) depend on the rotations \(R_i\), and the best rotations depend on the positions. The classic escape is alternating minimization: freeze one set, solve for the other, and repeat.
Store the rest configuration
Build one-ring neighborhoods, compute cotangent weights from the original geometry, assemble the sparse Laplacian \(L\), and factor it once, since it never changes.
User moves the handles
Anchored and dragged vertices take their user-given positions; everything else starts from wherever it currently is. This is the initial guess for \(\mathbf{p}'\).
Best rotation per cell
With positions frozen, find the \(R_i\) minimizing each cell's rigidity energy: a 3×3 covariance matrix and a small SVD per vertex.
Solve for positions
With rotations frozen, the optimal positions satisfy the sparse linear system \(L\mathbf{p}' = \mathbf{b}\). Repeat local/global 2-5 times, and each pass provably never increases the energy.
\(\alpha_{ij}\) and \(\beta_{ij}\) are the two angles opposite edge \((i, j)\). These weights account for triangle shapes, so the result doesn't depend on how the surface happens to be triangulated. They're computed once from the rest pose, so the energy always measures rigidity relative to the original geometry.
Each edge writes a symmetric pair of off-diagonal entries \(L_{ij} = L_{ji} = -w_{ij}\), and each diagonal entry collects its row's weights. Because cells are one-rings, a vertex only touches its immediate neighbors, so nearly every entry of \(L\) is zero, which is what makes the system fast to solve. When the user anchors a vertex, its row is replaced with the identity row and its known position moves into \(\mathbf{b}\): the anchor drops out of the optimization and stays pinned.
The best rotation for each cell falls out of the SVD of a small 3×3 covariance matrix \(S_i\), with a sign check on \(\det(V_iU_i^T)\) to rule out reflections. Rotation estimation uses Eigen's JacobiSVD per vertex.
Reading the diagram: each neighbor pairs a rest edge \(\mathbf{e}_{ij}\) (left) with its deformed counterpart \(\mathbf{e}'_{ij}\) (right, one pair highlighted). Their outer product is a 3×3 matrix that "votes" for the transform carrying one onto the other; the cotangent-weighted sum of all votes is the covariance \(S_i\). Its SVD strips away stretch and shear, leaving the pure rotation \(R_i\). The dashed ghost on the right shows the rest edges under \(R_i\) alone, and the small dotted gaps between ghost and actual edges are the non-rigid residuals the ARAP energy penalizes.
Setting \(\partial E/\partial \mathbf{p}' = 0\) with the rotations held fixed produces the Laplacian on the left and this vector on the right: each rest edge, rotated by the average of the two rotations that claim it (every edge belongs to two overlapping cells). The prefactored SimplicialLDLT Cholesky solve then recovers all three coordinate columns of \(\mathbf{p}'\) by back-substitution, with no refactoring ever, which is what keeps dragging interactive.
Key properties
Detail preservation
Local rotations capture bending and twisting, preventing the detail washout that plagues linear methods under large deformations.
Edge length fidelity
The optimization naturally resists stretching, so relative edge length error stays very low unless constraints force it.
No rig required
Physically plausible deformation from positional constraints alone, with no skeleton, weights, or setup needed.