Chapter IV.

Origami

Flat-foldable origami crease patterns are two-colorable.

Origami Objects

This class-style objects represent origami models. Underneath, each is a FOLD object, bound with methods that do origami things. Each inherits from the "parent" class, graph.

var origami = ear.graph()

This is the foundation for these objects, covered in detail in the graph chapter.

Origami

The origami object represents a foldable piece of paper. It will follow the same rules of folding that exist in our world.

var origami = ear.origami()
var origami = ear.origami()
origami.flatFold(line)

All fold operations orient themselves using a similar rule:

The fold direction is determined by the directionality of the line. For a flat-fold: "fold all flaps to the counter-clockwise side from the line's vector." (this corresponds to the normal in the normal parameterization)

origami.flatFold(line)

Currently, there is only one fold, the all-layers valley fold. In the future, more folds will be introduced here, like the reverse fold, squash, pedal.

Be mindful of Y-axis direction for SVG renders. "counter-clockwise" is such in the mathematical, +Y upwards context. The sketch above has an upwards +Y.

Crease Pattern

The crease pattern class foregoes imposing any rules of foldability and allows you to draw uninhibited on a flat canvas. It intends to be a replacement for commercial vector-based design software.

var origami = ear.cp()

The graph maintains planarity, so a few things are taken care of for you:

var cp = ear.cp()

The 3D visualization above is Origami Simulator.

Creasing is usually a two step process. Draw a shape, which returns a reference to a set of edges (if it was successful), then set the edges' attributes, such as mountain/valley.

cp.segment(0, 0, 1, 1).mountain()

These methods can also be chained.

If you know all the points, you can draw a crease pattern with numbers.

var cp = ear.cp()
...
cp.segment(0.0, 1.0, 0.25, 0.75).valley();

// center square
cp.rect(0.25, 0.25, 0.5, 0.5).valley();

// edge diagonals
cp.segment(0.5, 0.0, 0.75, 0.25).mountain();
cp.segment(1.0, 0.5, 0.75, 0.75).mountain();
...

Traditionally, a crease is not located using numbers, it's uncovered by folding, using the rules of origami.

Axioms

ear.axiom(number, params, boundary)

boundary is an optional parameter

The seven origami axioms are the different ways to make a crease exactly in place.

The purpose of the axiom method is to get back a line (or 2 or 3). Once you get this line, it can then be used as the input for a fold operation.

fold two points together

Axiom #
ear.axiom(2, params)

Each axiom takes a certain combination of points and lines; place them inside arrays, under their respective keys.

ear.axiom(2, { points: [...], lines: [...] })

Input

Output, given correct inputs

Points can be either arrays of numbers, or x,y objects. Lines are either parameterized with a vector and origin, or in u-d form.

{
  points: [
    [0.8, 0.75],
    { x: 0.5, y: 0.22 }
  ],
  lines: [
    { vector: [0.75, 0.25], origin: [0.5, 0.5] }
  ],
}

Or use Rabbit Ear's vector and line types.

Some axioms have multiple solutions; therefore, the return value for every axiom method is an array of lines.

Axioms within a boundary

In reality, an axiom is constructed on a sheet of paper, and should only be constructible if all necessary geometry lies on the paper.

ear.axiom(number, params, boundary)

boundary can be either a polygon or a FOLD graph.

Provide a third argument for the boundary and non-constructible solutions will be filtered out.

fold two points together

Axiom #

Try moving the points and lines outside the boundary of the paper.

Axioms can fail in two ways: they can fail due to input parameters being outside the boundary, or they can be non-constructible simply due to their input parameters. Not all can fail the latter, but Axiom 5 can be a good example of it.

origami axiom 5

Single Vertex

A single-vertex refers to one vertex with its N-number of edges and faces around it. Complex problems on crease patterns can be tackled piecewise by focusing on single-vertices.

ear.vertex
ear.vertex.foldAngles4(sectors, assignments)

The continuous folding motion in 3D for a degree-four single vertex.

Kawasaki's Theorem

ear.vertex.alternatingSum(array)

For a vertex to be flat-foldable, the sum of alternating sector angles must be 180°. This implies an even number of creases.

Given an odd number of creases, we can ask the computer to find creases that can satisfy a solution.

Only one result is required to be added to the set of input creases, and Kawasaki's theorem will be satisfied.

ear.vertex.kawasakiSolutions(vectors)
ear.vertex.kawasakiSolutionsRadians(radians)

This set of results has a cool property: any number of them, whether it be one or many, can be added to the single vertex and the result will be valid according to Kawasaki's theorem.

These methods require sorted vectors/radians as an input. Use these methods below:

ear.math.counterClockwiseOrder2(vectors)
  .map(i => vectors[i])
ear.math.counterClockwiseOrderRadians(angles)
  .map(i => angles[i])

Sort radially 2D vectors or vectors as radians.

Given a crease pattern template like this origami twist, if we maintain the graph structure, we can use Kawasaki alone to create variation in form.

var valleys = ear.vertex.kawasakiSolutions(mountains)

In the example above, Kawasaki's theorem is used to solve the angle of the valley fold (dashed) lines.

Maekawa's theorem

For a vertex to be flat-foldable, the number of mountain and valley creases should differ by 2.

This method will replace all unassigned creases "U" with mountain or valley, returning all permutations that satisfy Maekawa's Theorem.

ear.vertex.maekawaAssignments(["M", "U", "U", "U"])
[
  ["M", "V", "V", "V"],
  ["M", "V", "M", "M"],
  ["M", "M", "V", "M"],
  ["M", "M", "M", "V"],
]

This method is pretty simple on its own, but will become more powerful when paired with layer-order solvers.

Validators

Both Kawasaki and Maekawa can be quickly validated on all vertices of a crease pattern using these methods.

ear.vertex.validateKawasaki(graph, epsilon)
ear.vertex.validateMaekawa(graph)

These methods return an array of invalid vertex indices.

Folding a Crease Pattern

By using a minimum spanning tree, an origami can be "folded" by reflecting each face across the adjacent edge along each path. Adjacent faces will naturally fall back into place next to their neighbors.

origami.folded(4)
origami.flatFolded(4)

folded works in both 2D or 3D. flatFolded is for 2D origami only, and is slightly more precise.

The result of these methods does not offer the renderer any direction about which face to draw first, resulting in the rendering on the right.

Before solving the layer order

A layer order provides the renderer with the correct z-ordering for the faces.

Layer Order

ear.layer

Single Vertex Layer Order

If layer solvers are new to you, a gentle introduction might be a single-vertex.

Imagine a side-view of a strip of paper containing 180-degree crease lines: mountain, valley, or flat.

var lengths = []
var assignments = []
ear.layer.singleVertexSolver(lengths, assignments)

This visualization rounds the corners in a manner that can sometimes appear like a layer intersection. This is only a visualization error.

The singleVertexSolver requires a list of scalars and a list of crease assignments. If multiple solutions exist it will find them all.

The solver will reject any solutions which self-intersect.

As far as the solver is concerned, a linear strip is the same thing as a single-vertex. The list of scalars is the sector angles.

Toggle mountain and valley creases. Can you find other valid combinations?

Of course, the main difference is that the strip loops back around. But all that this requires is the absence (or presence) of "B" boundary assignments.

ear.layer.singleVertexSolver(sectors_angle, assignments)

The result is an array of faces_layers.

The following method will solve both the layers and the assignments, and return an array of all possible solutions.

press to toggle through solutions

ear.layer.assignmentSolver(sectors_angle)

This method will work even if a partial set of assignments is known; simply mix "U" (unassigned) in the assignment array wherever necessary.

ear.layer.assignmentSolver(sectors_angle, assignments)

The assignment solver will only try to replace unassigned with valleys and mountains, not flats.

Dynamically calculating Kawasaki's theorem, in action.

var bottom_left = ear.vertex.kawasakiSolutions(three_vectors)
var solution = ear.layer.assignmentSolver(sectors_angle)
cp.edges_assignment = solution[0].assignment
cp.faces_layer = solution[0].layer

The above sketch demonstrates Kawasaki's theorem. The layer-order can be solved by calculating the sector angles and handing it off to the assignment solver which solves both layers and assignments. Or, we can instead use the much easier, and much more powerful global solver.

Global Layer Order

The traditional Kabuto Samurai Helmet and its 9 valid layer arrangements.

ear.layer.solver(graph)

This method takes a folded origami, not a crease pattern.

Solving a crease pattern's global layer order is hard. There are many approaches. This library implements an algorithm designed by Jason Ku which works well for SVG renderings.

The first step is, for every face, gather all other faces which overlap this face.

Face-face overlap matrix

Every overlapping pair of faces is a variable that needs to be solved. Not all possible pairs of faces needs to be solved.

Yellow is an unsolved condition. The algorithm goes as such:

Faces above and below the selected face.

(a) solved neighbor faces, (b) solved all non-toggleable flaps, (c) one of the nine solutions.

Some crease patterns finish after (b). The Kabuto is an origami model with multiple layer flap arrangements, requiring recusively branching and guessing and checking (c).

Layer Solutions

The solution to a layer order contains both the certain relationships and all of its branches. To compile a solution you must choose one from each branch. To do this, you will need to know the number of branches, and how many options are available on each branch:

ear.layer.solver(graph).count()
↳ [3, 3]

This origami (Kabuto) has two branches, each with three options.

Generally, the workflow might be (1) solve the layer order, (2) check the branch count, (3) choose branches.

var result = ear.layer.solver(graph) // 1
console.log(result.count()) // 2
cp.facesOrder = result.facesOrder(1, 2) // 3

An empty parenthesis will simply choose the first from each branch.

ear.layer.solver(graph).facesOrder(0, 0)
// same as
ear.layer.solver(graph).facesOrder()

Empty parenthesis is, I don't care which result, just give me one.

And finally, the solution can be compiled into one of a few forms, a faceOrders, a faces_layer, or either of these but with an array of all permutations of branch choices.

ear.layer.solver(graph).faceOrders()
ear.layer.solver(graph).facesLayer()
ear.layer.solver(graph).allFaceOrders()
ear.layer.solver(graph).allFacesLayer()

Cycles

The SVG renderer keeps faces intact and assigns one layer per face, making three or more cyclically-overlapping faces impossible to render; unless, the faces are divided with flat crease lines.

This relies on a crease pattern already having this exact placement of these flat creases. An algorithm which takes care of this would be nice to develop.