Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Loops: Boundaries as Ordered Structure

An edge connects two vertices.

A loop turns a sequence of connected edges into a boundary.

In MatterScript, a loop is constructed by referencing previously defined edges:

l0<loop($e0, $e1, $e2, $e3)>

The important detail is that a loop is not simply a collection of edges. The edges are supplied in order, and that order defines how the boundary is traversed.

For example:

p0 ──e0──► p1
             │
             e1
             ▼
             p2
             │
             e2
             ▼
             p3
             │
             e3
             ▼
             p0

The loop represents the closed path:

p0 → p1 → p2 → p3 → p0

This establishes the next level of MatterScript’s geometric hierarchy:

POINT
   │
   │ position
   ▼
EDGE
   │
   │ connectivity
   ▼
LOOP
   │
   │ boundary
   ▼
FACE
   │
   │ surface
   ▼
MESH

A vertex establishes where.

An edge establishes between.

A loop establishes around.


1. A Loop Is an Ordered Boundary

The simplest loop consists of three or more edges whose endpoints form a continuous closed path.

A square can be described as:

p0<point(0.0, 0.0)>
p1<point(1.0, 0.0)>
p2<point(1.0, 1.0)>
p3<point(0.0, 1.0)>

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>
e2<edge($p2, $p3)>
e3<edge($p3, $p0)>

l0<loop($e0, $e1, $e2, $e3)>

The four edges are not passed to loop(...) in arbitrary order.

They describe a traversal:

e0 → e1 → e2 → e3

which corresponds to:

p0 → p1 → p2 → p3 → p0

The loop therefore carries more information than the existence of four edges.

It carries their ordered connectivity.


2. Loops Use the Same Binding Model

No new reference mechanism is needed for loops.

The edges are already named:

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>
e2<edge($p2, $p3)>
e3<edge($p3, $p0)>

The loop simply references them:

l0<loop($e0, $e1, $e2, $e3)>

This follows the same define-once, reference-many discipline used by vertices and edges.

point
  │
  └── named binding

edge
  │
  └── named binding

loop
  │
  └── named binding

Each level can therefore be addressed independently.

The $ sigil always means the same thing:

Look up an existing binding.

There is no special syntax for geometry composition.


3. A Loop Consumes Edges

The type relationship between the primitives is straightforward:

point(...)
    ↓
edge(point, point)
    ↓
loop(edge, edge, edge, ...)
    ↓
face(loop)

An edge requires two point handles.

A loop requires edge handles.

A face will require a loop handle.

This creates a progressively richer structure without requiring the higher-level primitive to duplicate the lower-level geometry.

For example:

e0<edge($p0, $p1)>

does not copy p0 or p1.

Likewise:

l0<loop($e0, $e1, $e2, $e3)>

does not copy the edges.

The loop is a new geometric entity whose structure is defined by its references to existing edges.


4. Closure Is the Essential Property

The defining characteristic of a loop is closure.

For:

l0<loop($e0, $e1, $e2, $e3)>

the resolver checks that each edge connects correctly to the next.

If:

e0 = p0 → p1
e1 = p1 → p2
e2 = p2 → p3
e3 = p3 → p0

then the boundary is continuous and closed:

p0 → p1 → p2 → p3 → p0

The final edge returns to the starting point of the first edge.

That final connection is what turns a path into a loop.

Conceptually:

OPEN PATH

p0 → p1 → p2 → p3


CLOSED PATH

p0 → p1 → p2 → p3
↑               │
└───────────────┘

A loop is therefore not simply “some edges.”

It is an edge sequence that successfully returns to where it began.


5. Continuity Between Edges

For a loop to be continuous, the endpoint of each edge must match the starting point of the next.

Consider:

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>

These edges connect correctly:

p0 → p1 → p2

The endpoint of e0 is p1.

The starting point of e1 is also p1.

Now consider:

e0<edge($p0, $p1)>
e1<edge($p2, $p3)>

These do not form a continuous path:

p0 → p1

p2 → p3

There is no connection between p1 and p2.

When the loop resolver encounters such a sequence, it reports:

error.DiscontinuousLoop

The loop primitive therefore performs an important piece of structural validation.

It does not merely record the author’s intention.

It verifies that the referenced edges actually form the boundary being claimed.


6. The Final Edge Must Close the Loop

Continuity between adjacent edges is not enough.

The final edge must also return to the first edge’s starting point.

Consider:

e0 = p0 → p1
e1 = p1 → p2
e2 = p2 → p3
e3 = p3 → p4

This is continuous:

p0 → p1 → p2 → p3 → p4

But it is not closed.

The path ends at p4.

It began at p0.

If the final edge does not return to p0, the resolver reports:

error.UnclosedLoop

The distinction is useful:

continuous ≠ closed

A continuous path can remain open.

A loop must be both continuous and closed.


7. A Loop Needs at Least Three Edges

The current implementation also rejects loops containing fewer than three edges.

For example:

l0<loop($e0, $e1)>

is not a valid loop.

It produces:

error.DegenerateLoop

The minimum valid structure is therefore three edges, such as a triangle:

p0<point(0.0, 0.0)>
p1<point(1.0, 0.0)>
p2<point(0.5, 1.0)>

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>
e2<edge($p2, $p0)>

l0<loop($e0, $e1, $e2)>

The resulting traversal is:

p0 → p1 → p2 → p0

This is the smallest ordinary closed polygonal boundary represented by the current loop primitive.


8. Source Order Becomes Geometric Order

MatterScript geometry is resolved in source order.

That means the normal construction sequence is:

points
  ↓
edges
  ↓
loops
  ↓
faces

For a loop, the referenced edges must already exist:

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>
e2<edge($p2, $p3)>
e3<edge($p3, $p0)>

l0<loop($e0, $e1, $e2, $e3)>

The resolver walks the already-parsed definition from top to bottom.

When it reaches l0, the four edge bindings are already available in the geometry binding table.

This is the same forward-resolution rule used by the lower-level geometry primitives.

A loop cannot currently refer to an edge that has not yet been bound.


9. The Order of Edges Matters

Consider these edges:

e0 = p0 → p1
e1 = p1 → p2
e2 = p2 → p3
e3 = p3 → p0

The natural loop is:

l0<loop($e0, $e1, $e2, $e3)>

because the edges form:

p0 → p1 → p2 → p3 → p0

The loop therefore contains an ordered boundary.

This is different from treating the edges as an unordered set.

The same four edges contain enough information to describe the square, but the loop declaration tells the resolver how those edges are intended to be traversed.

This ordering is what allows closure to be checked locally.


10. Loops Turn Connectivity into Topology

An edge tells us that two points are connected.

A loop tells us that several such connections form a boundary.

That is an important conceptual transition.

POINT

"I exist here."


EDGE

"I connect these two positions."


LOOP

"I traverse these connections and return to where I started."

The geometry is becoming topological structure.

The coordinates are still present, but they are no longer the whole description.

The relationship between entities has become part of the model.

MatterScript can therefore move from numerical geometry toward structural geometry using the same mechanism throughout:

coordinates
    ↓
named entities
    ↓
named relationships
    ↓
ordered boundaries

11. A Loop Does Not Recreate Its Edges

The loop:

l0<loop($e0, $e1, $e2, $e3)>

does not contain four new line segments.

It refers to four existing edge bindings.

This distinction matters because the same underlying points can participate in many different geometric relationships.

For example, a point can belong to multiple edges:

        e1
       /
      p1
     /  \
   e0    e2
   /      \
 p0        p2

Likewise, a geometry definition can contain many loops built from different edge sequences.

The graph remains compositional because each entity is defined once and referenced thereafter.


12. Shared Edges: An Important Current Design Choice

When two faces meet, their boundaries can contain geometrically coincident edges.

A conventional watertight mesh often treats such a segment as a shared edge traversed in opposite directions by neighboring faces.

The current MatterScript spatial implementation takes a simpler approach.

Each face should have its own edge binding for a shared physical segment.

For example:

e_front<edge($p0, $p1)>

and elsewhere:

e_back<edge($p0, $p1)>

These edges are geometrically coincident, but they are separate source-level bindings.

This is deliberate.

The current loop closure logic validates one loop at a time. It does not reason about two neighboring loops needing to traverse a shared edge in opposite directions.

Giving each loop its own edge binding keeps the closure problem local:

Face A
  └── e_a0
      e_a1
      e_a2
      e_a3
          ↓
        loop A


Face B
  └── e_b0
      e_b1
      e_b2
      e_b3
          ↓
        loop B

This avoids introducing a separate concept of “reverse this edge” into the current geometry model.

It is a small amount of deliberate duplication in exchange for simple, independently verifiable loops.


13. Loops Are Independent Geometry Entities

Once resolved, the loop receives its own geometry handle and is recorded in the geometry binding table under its source name.

Conceptually:

MatterScript

l0<loop($e0, $e1, $e2, $e3)>
          │
          ▼
   geometry resolver
          │
          ▼
     SpatialGraph
          │
          ▼
       LoopId

The source-level name remains important:

$l0

can subsequently be used by a face:

f0<face($l0)>

The face does not need to reconstruct the boundary.

It simply references the already validated loop.

This gives the geometry hierarchy a clean separation of responsibilities:

edge
  └── establishes connectivity

loop
  └── validates and names the boundary

face
  └── gives that boundary surface meaning

14. The Loop Is the Boundary Between Geometry and Surface

Before a loop exists, we have connected line segments.

After a loop exists, we have a closed boundary that can enclose a surface.

This is why loop(...) occupies such an important place in the geometry hierarchy.

Points
  ↓
positions


Edges
  ↓
connections


Loops
  ↓
boundaries


Faces
  ↓
surfaces

The loop is therefore the structural bridge between wire-like connectivity and surface geometry.

A face can now be defined simply:

f0<face($l0)>

The face does not need the points again.

It does not need the edges again.

The loop already contains the ordered boundary relationship.


15. From Loop to Face

Consider the complete sequence:

p0<point(0.0, 0.0)>
p1<point(1.0, 0.0)>
p2<point(1.0, 1.0)>
p3<point(0.0, 1.0)>

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>
e2<edge($p2, $p3)>
e3<edge($p3, $p0)>

l0<loop($e0, $e1, $e2, $e3)>

f0<face($l0)>

The construction can be read as a sequence of increasingly meaningful statements:

 p0, p1, p2, p3
        │
        ▼
   four positions
        │
        ▼
 e0, e1, e2, e3
        │
        ▼
 four connections
        │
        ▼
        l0
        │
        ▼
 closed boundary
        │
        ▼
        f0
        │
        ▼
      surface

Each layer uses the result of the previous layer.

That is the central construction pattern of MatterScript spatial geometry.


16. Loops in Three Dimensions

The loop primitive is not limited to flat two-dimensional examples.

The same construction applies to boundaries of faces in three-dimensional geometry.

For example, a cube face can be described by four three-dimensional points:

p0<point(0.0, 0.0, 0.0)>
p1<point(1.0, 0.0, 0.0)>
p2<point(1.0, 1.0, 0.0)>
p3<point(0.0, 1.0, 0.0)>

followed by:

e0<edge($p0, $p1)>
e1<edge($p1, $p2)>
e2<edge($p2, $p3)>
e3<edge($p3, $p0)>

and then:

l0<loop($e0, $e1, $e2, $e3)>

The loop itself still represents the same kind of object: an ordered, closed boundary.

The coordinates happen to live in three-dimensional space.

This is one reason the same primitive vocabulary can serve both spatial2d and spatial3d.


17. The Complete Geometry Pipeline

At this point, the construction pipeline is becoming explicit:

.ms.ipl source
      │
      ▼
 IPL parser
      │
      ▼
Spatial binding resolution
      │
      ├── points
      ├── edges
      ├── loops
      └── faces
      │
      ▼
SpatialGraph
      │
      ▼
Triangulation
      │
      ▼
TriangulatedMesh
      │
      ▼
PLY

The loop is resolved before triangulation.

That matters because triangulation operates on the higher-level geometry structure rather than asking the mesh exporter to infer boundaries from an unstructured collection of segments.

The boundary is established explicitly in the MatterScript source.


18. A Complete Loop Example

Here is a complete triangular boundary:

Triangle[()()
    @domain(spatial2d)

    p0<point(0.0, 0.0)>
    p1<point(1.0, 0.0)>
    p2<point(0.5, 1.0)>

    e0<edge($p0, $p1)>
    e1<edge($p1, $p2)>
    e2<edge($p2, $p0)>

    l0<loop($e0, $e1, $e2)>

    f0<face($l0)>
]

The structure is:

          p2
         /  \
       e1    e2
       /      \
     p1──e0──►p0

with traversal:

p0 → p1 → p2 → p0

The three edges form a continuous path.

The final edge returns to the starting point.

The loop is therefore valid.

The face can then consume the loop.


19. Why Loops Matter to MatterScript

A generic geometry library could treat a polygon as simply a list of coordinates.

MatterScript instead exposes the intermediate structures explicitly:

Point
Edge
Loop
Face

That choice makes the construction process visible.

The programmer can see:

  • where positions are established,
  • where connectivity is introduced,
  • where closure is validated,
  • where a boundary becomes a surface.

The geometry is therefore not just an output format.

It is represented as a hierarchy of named, composable relationships.

This is particularly important to MatterScript’s larger goal of bringing physical structure into the same language used to describe computation.

A loop is not merely something needed to draw a polygon.

It is an explicit statement about boundary structure.


20. From Boundaries to Surfaces

The geometric progression is now complete enough to state directly:

Vertices define position.

Edges define connectivity.

Loops define boundaries.

Faces define surfaces.

The loop is the point at which connectivity becomes a meaningful enclosed structure.

That structure can then be consumed by face(...):

f0<face($l0)>

and ultimately participate in triangulation and mesh generation.

The hierarchy is:

          FACE
            ▲
            │
          LOOP
            ▲
            │
           EDGE
            ▲
            │
          POINT

Each layer adds structure without abandoning the entities below it.


21. Looking Ahead: Faces

A loop establishes a closed boundary.

The next question is what that boundary encloses.

That is the role of a face.

In MatterScript:

f0<face($l0)>

takes the named loop and promotes its boundary into a surface-level geometric entity.

The construction sequence has therefore reached the next conceptual boundary:

POINT
  │
  │ position
  ▼
EDGE
  │
  │ connectivity
  ▼
LOOP
  │
  │ boundary
  ▼
FACE
  │
  │ surface
  ▼
MESH

A point tells us where.

An edge tells us between.

A loop tells us around.

A face will tell us across.

Drafting in progress…