2  Geometry of a Crease

NoteLearning Objectives
  • Represent a fold as a rotation matrix and compute panel positions after folding
  • State and apply Kawasaki’s theorem for flat-foldability at a vertex
  • Apply Maekawa’s theorem to count mountain and valley folds
  • Write Python functions to check both conditions numerically

A piece of paper has three properties that make it mathematically interesting: it is flat, it is inextensible, and it can be folded. Flatness means the rest configuration is a plane. Inextensibility means distances between points on the surface are preserved during folding — the paper does not stretch. Foldability means that along a crease line, one panel can rotate relative to another by an arbitrary angle.

These three properties together severely constrain what shapes a folded sheet can take. Almost all of the content of this book follows from working out those constraints carefully.

2.1 The Fold Angle

Consider a single straight crease running across a sheet. The crease divides the sheet into two planar panels, which we call the left panel and the right panel (or panel 1 and panel 2). Define the fold angle \(\phi\) as the dihedral angle between the two panels, measured such that:

\[\phi = 0 \quad \text{(flat, unfolded)} \tag{2.1}\]

\[\phi = \pi \quad \text{(fully folded, panels touching)} \tag{2.2}\]

The sign of \(\phi\) distinguishes mountain folds (panel 2 folds up relative to panel 1, \(\phi > 0\)) from valley folds (\(\phi < 0\)). For a single crease, this sign convention is arbitrary — we can always flip the sheet — but once we have a vertex where multiple creases meet, the relative signs matter.

2.2 Rotation Matrices at Fold Lines

Let the crease lie along the unit vector \(\hat{e}\). If panel 1 is fixed in space, then panel 2 is obtained by rotating panel 1 about \(\hat{e}\) by angle \(\phi\). The rotation is given by the Rodrigues formula:

\[R(\hat{e}, \phi) = I \cos\phi + (1 - \cos\phi)\hat{e}\hat{e}^T + \sin\phi\, [\hat{e}]_\times \tag{2.3}\]

where \([\hat{e}]_\times\) is the skew-symmetric cross-product matrix:

\[[\hat{e}]_\times = \begin{pmatrix} 0 & -e_z & e_y \\ e_z & 0 & -e_x \\ -e_y & e_x & 0 \end{pmatrix} \tag{2.4}\]

Code
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection

def rotation_matrix(axis, angle):
    """Rodrigues rotation matrix: rotate by angle about axis."""
    e = np.asarray(axis, dtype=float)
    e = e / np.linalg.norm(e)
    c, s = np.cos(angle), np.sin(angle)
    skew = np.array([
        [ 0,    -e[2],  e[1]],
        [ e[2],  0,    -e[0]],
        [-e[1],  e[0],  0   ]
    ])
    return c * np.eye(3) + (1 - c) * np.outer(e, e) + s * skew


def fold_panel(panel_vertices, crease_point, crease_axis, angle):
    """Rotate panel_vertices about a crease line by angle."""
    verts = np.asarray(panel_vertices, dtype=float)
    R = rotation_matrix(crease_axis, angle)
    # Translate so crease_point is origin, rotate, translate back
    return (R @ (verts - crease_point).T).T + crease_point


# Demo: fold a single-crease sheet
fig = plt.figure(figsize=(10, 4))

fold_angles = [0, np.pi / 4, np.pi / 2, 3 * np.pi / 4]
panel1 = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]])
panel2_flat = np.array([[0, 0, 0], [-1, 0, 0], [-1, 1, 0], [0, 1, 0]])

for i, phi in enumerate(fold_angles):
    ax = fig.add_subplot(1, 4, i + 1, projection='3d')
    crease_axis = np.array([0, 1, 0])
    crease_point = np.array([0, 0, 0])
    panel2 = fold_panel(panel2_flat, crease_point, crease_axis, -phi)

    for verts, color in [(panel1, 'steelblue'), (panel2, 'coral')]:
        poly = Poly3DCollection([verts], alpha=0.6, facecolor=color, edgecolor='k', linewidth=0.5)
        ax.add_collection3d(poly)

    ax.set_xlim(-1.2, 1.2)
    ax.set_ylim(-0.2, 1.2)
    ax.set_zlim(-1.2, 0.2)
    ax.set_title(f'φ = {phi:.2f}', fontsize=9)
    ax.set_axis_off()

plt.suptitle('Single-crease fold at increasing fold angles', y=1.02)
plt.tight_layout()
plt.show()

2.3 Vertices and Multi-Crease Constraints

The interesting geometry begins at a vertex — a point where two or more creases meet. At a vertex with \(n\) creases, there are \(n\) fold angles \(\phi_1, \ldots, \phi_n\) and \(n\) sector angles \(\alpha_1, \ldots, \alpha_n\), where \(\alpha_i\) is the angle between crease \(i\) and crease \(i+1\) measured in the flat (unfolded) sheet.

The sector angles satisfy:

\[\sum_{i=1}^{n} \alpha_i = 2\pi \tag{2.5}\]

because they partition the full angle around the vertex.

For the sheet to be foldable (no tearing, no self-intersection at the vertex), the composition of rotations around the vertex must close — the last panel must connect back to the first. This closure condition is the source of all the flat-foldability constraints.

2.4 Kawasaki’s Theorem

For a vertex with \(2k\) creases (flat-foldable vertices always have an even number of creases), flat-foldability requires:

\[\sum_{i=1}^{k} \alpha_{2i-1} = \sum_{i=1}^{k} \alpha_{2i} = \pi \tag{2.6}\]

In words: the alternating sector angles must each sum to \(\pi\). For a 4-crease vertex, this means:

\[\alpha_1 + \alpha_3 = \alpha_2 + \alpha_4 = \pi \tag{2.7}\]

This is Kawasaki’s theorem (Kawasaki 1989), and it is both necessary and sufficient for flat-foldability (in the local, single-vertex sense).

Code
def check_kawasaki(sector_angles):
    """
    Check Kawasaki's theorem for a vertex.
    sector_angles: list of angles in radians, alternating around the vertex.
    Returns (satisfied, odd_sum, even_sum).
    """
    alphas = np.asarray(sector_angles)
    n = len(alphas)
    if n % 2 != 0:
        return False, None, None
    odd_sum  = np.sum(alphas[0::2])   # indices 0, 2, 4, ...
    even_sum = np.sum(alphas[1::2])   # indices 1, 3, 5, ...
    satisfied = np.isclose(odd_sum, np.pi) and np.isclose(even_sum, np.pi)
    return satisfied, odd_sum, even_sum


# Example: standard 4-crease vertex
alphas = [np.pi/3, 2*np.pi/3, np.pi/3, 2*np.pi/3]
ok, s1, s2 = check_kawasaki(alphas)
print(f"Sector angles: {[f'{a:.3f}' for a in alphas]}")
print(f"Kawasaki satisfied: {ok}")
print(f"Odd sum:  {s1:.4f}  (π = {np.pi:.4f})")
print(f"Even sum: {s2:.4f}  (π = {np.pi:.4f})")
Code
# Visualize: which combinations of (α1, α2) satisfy Kawasaki for a 4-crease vertex?
# Given α1 + α3 = π  →  α3 = π - α1
# Given α2 + α4 = π  →  α4 = π - α2
# Constraint: α1 + α2 + α3 + α4 = 2π  →  always satisfied for any α1, α2.
# So for a 4-crease vertex, any (α1, α2) with α1, α2 ∈ (0, π) works.

alpha1_vals = np.linspace(0.1, np.pi - 0.1, 200)
alpha2_vals = np.linspace(0.1, np.pi - 0.1, 200)
A1, A2 = np.meshgrid(alpha1_vals, alpha2_vals)

# Compaction ratio proxy: total variation in fold space
# For illustration, just show the feasible region
fig, ax = plt.subplots(figsize=(5, 4))
ax.fill_between(alpha1_vals, 0.1, np.pi - 0.1, alpha=0.15, color='steelblue',
                label='Kawasaki feasible region')
ax.set_xlabel('α₁ (rad)')
ax.set_ylabel('α₂ (rad)')
ax.set_title('4-crease vertex: Kawasaki feasible region')
ax.set_xlim(0, np.pi)
ax.set_ylim(0, np.pi)
ax.axvline(np.pi / 2, color='gray', linestyle='--', linewidth=0.8, label='α₁ = π/2')
ax.axhline(np.pi / 2, color='gray', linestyle=':', linewidth=0.8, label='α₂ = π/2')
ax.legend(fontsize=8)
plt.tight_layout()
plt.show()

2.5 Maekawa’s Theorem

Kawasaki tells us about the angles. Maekawa’s theorem tells us about the fold directions: at any flat-foldable vertex, the number of mountain folds \(M\) and valley folds \(V\) must satisfy:

\[|M - V| = 2 \tag{2.8}\]

For a 4-crease vertex, this means either \(M=3, V=1\) or \(M=1, V=3\). You cannot have \(M=2, V=2\) at a flat-foldable vertex.

Code
def check_maekawa(fold_types):
    """
    Check Maekawa's theorem.
    fold_types: list of +1 (mountain) or -1 (valley).
    Returns (satisfied, M, V).
    """
    types = np.asarray(fold_types)
    M = np.sum(types == 1)
    V = np.sum(types == -1)
    satisfied = abs(M - V) == 2
    return satisfied, int(M), int(V)


cases = [
    ([1,  1,  1, -1], "M=3, V=1"),
    ([1, -1,  1, -1], "M=2, V=2"),
    ([-1, -1, -1, 1], "M=1, V=3"),
]
for types, label in cases:
    ok, M, V = check_maekawa(types)
    print(f"{label:12s}  →  Maekawa satisfied: {ok}")

2.6 A Complete Vertex Checker

Code
def is_flat_foldable_vertex(sector_angles, fold_types):
    """
    Check both Kawasaki and Maekawa for a single vertex.
    sector_angles: alternating sector angles in radians
    fold_types: +1 mountain, -1 valley, one per crease
    """
    kaw_ok, s1, s2 = check_kawasaki(sector_angles)
    mae_ok, M, V   = check_maekawa(fold_types)
    return {
        'kawasaki': kaw_ok,
        'maekawa':  mae_ok,
        'flat_foldable': kaw_ok and mae_ok,
        'odd_angle_sum':  s1,
        'even_angle_sum': s2,
        'mountains': M,
        'valleys':   V,
    }


# Standard Miura-ori vertex
alphas = [np.pi/4, 3*np.pi/4, np.pi/4, 3*np.pi/4]
folds  = [1, -1, 1, -1]   # M, V, M, V
result = is_flat_foldable_vertex(alphas, folds)
for k, v in result.items():
    print(f"  {k}: {v}")

2.7 Summary

  • A fold is a rotation about a crease line, parameterized by fold angle \(\phi \in [0, \pi]\).
  • Rodrigues’ formula gives the rotation matrix for any axis and angle.
  • At a multi-crease vertex, flat-foldability requires Kawasaki’s theorem (alternating sector angles sum to \(\pi\)) and Maekawa’s theorem (\(|M - V| = 2\)).
  • Both conditions can be checked numerically with simple array operations.

2.8 Further Reading

Demaine and O’Rourke (2007) provides rigorous proofs of Kawasaki’s and Maekawa’s theorems (Chapters 11–12). Huffman (1976) is the original paper connecting curvature to crease geometry and is surprisingly readable.

2.9 Exercises

  1. Rodrigues by hand. For a crease along \(\hat{e} = \hat{y} = (0,1,0)\) and fold angle \(\phi = \pi/2\), write out \(R(\hat{e}, \phi)\) using Equation 2.3. Apply it to the vector \((1, 0, 0)\) and verify the result makes geometric sense.

  2. Three-crease vertex. Can a vertex with exactly three creases be flat-foldable? Use Kawasaki’s theorem to argue why or why not.

  3. Kawasaki violation. Write a Python function that, given a set of sector angles that violate Kawasaki’s theorem, returns the minimum perturbation to any single sector angle that restores the condition. (Hint: the answer is the difference between the actual alternating sums and \(\pi\), distributed optimally.)

  4. Maekawa patterns. For a 6-crease vertex, list all \((M, V)\) combinations that satisfy Maekawa’s theorem. How many mountain/valley assignment patterns are there for each valid \((M, V)\) pair?

  5. Closure check. Implement a function that, given sector angles and fold angles for a 4-crease vertex, multiplies the four rotation matrices in sequence and checks whether the product is the identity. Test it on the Miura-ori vertex from the code block above for \(\phi = \pi/4\).