# The Engineering behind Figma’s Vector Networks

*by*Phil Tadros

Adobe Illustrator launched the pen device again in 1987 as a device for creating and modifying paths. Since then the pen device has turn out to be extremely widespread, a lot so that’s has turn out to be the de facto icon of the graphic design trade.

The pen device’s performance hasn’t modified considerably within the 30 years since its introduction. Simply click on and drag to create clean curves. Designers have discovered to work with it, and round its idiosyncrasies.

The pen device

However Figma felt like they may enhance some features of how the pen device labored, so that they had a go at redesigning it. As a substitute of it getting used to work with conventional paths, they improved the pen device by creating what they name Vector Networks.

On this publish we’ll undergo what Vector Networks are and what issues they attempt to remedy. After we have outlined what Vector Networks are, we’ll check out among the engineering challenges you’ll face should you have been to take a stab at implementing them.

This publish might be regarded as an introduction to a very fascinating downside house, and as a useful resource for folks fascinating in making use of some features of Vector Networks for future purposes. I hope it succeeds in offering worth to each builders being launched to new ideas and concepts, and to designers fascinating in studying extra concerning the device they know and love.

I’ll begin off by laying out the core ideas behind the pen device, and from there we’ll transfer onto Figma’s Vector Networks.

## Paths

The pen device is used to create and manipulate paths.

In case you’ve each labored with graphics software program like Illustrator earlier than, you’ve got labored with paths. Paths are a collection of strains and curves that will or could not type a loop.

Some paths

The trail to the left loops, whereas the trail to the appropriate does not. Each of those are legitimate paths.

The primary attribute of paths is that they type a single steady unbroken chain. Which means that every node can solely be related to at least one or two different nodes.

Not legitimate paths

Nevertheless, you might assemble these shapes from a number of paths should you place them appropriately collectively.

A number of paths are used to create extra advanced shapes

From a mixture of paths, you’ll be able to create any form possible.

This beer glass, for instance, is only a mixture of 5 completely different paths positioned and scaled a sure manner.

### The constructing blocks of paths

A path is made up of two issues, factors and contours.

Factors and contours

The factors are often known as **nodes** (or vertices) and the strains are referred to as **edges**.

Collectively, they make a path

Any path might be described as a listing of nodes and edges.

This path might be described because the collection of nodes `(0, 1, 2, 3, 4)`

. It is also regarded as the collection of the sides that composed it. That checklist of edges can be `(0, 1)`

, `(1, 2)`

, `(2, 3)`

, `(3, 4)`

, `(4, 0)`

.

You may consider this just like the dot to dot puzzles that you just used to do as a child: Draw the sides of the trail within the order that the factors lay out.

However as an alternative of a child drawing strains between numbered factors on a paper, a chilly calculating machine does it alongside the cartesian coordinate system.

## Edges

An edge is a connection between a pair of nodes. Visually, edges are a line from node `a`

to node `b`

.

However that line might be drawn in numerous other ways. How do you describe these various kinds of strains?

Edges fall into two classes, straight and curvy.

Straight edges are so simple as they appear, only a line from `a`

to `b`

. However how are these curvy edges outlined?

### Bezier curves

Curvy edges are bezier curves. Bezier curves are a particular sort of curve outlined by 4 factors.

The positions of the 2 nodes in our edge make up the beginning and finish factors of the curve. Every of the 2 nodes has a *management level*.

In most purposes, these management factors are proven as *handles* that stretch from their respective node. These handles are used to regulate the form of the curve.

Bezier curves might be chained to make extra advanced shapes {that a} single curve cannot draw by itself. They will also be mixed with straight strains to make some cool designs.

However what precisely are the handles doing? How do the handles inform the pc to attract the curve prefer it does?

Computer systems draw curves by splitting them into straight strains and drawing the person strains.

The extra strains you break up a curve into, the smoother the curve turns into.

So to attract the curve we have to know the best way to get the completely different factors that make up the curve. If we compute sufficient of them, we get a clean curve.

### Computing a degree on a bezier curve

Let’s compute the purpose at 25% level of the curve. We will begin by connecting the management factors with a 3rd blue line.

Then for every blue line, we draw a blue dot on the 25% level of the road.

Subsequent, draw two inexperienced strains between the three blue dots.

And we repeat the identical step as we did with the blue dots. Draw inexperienced factors on the 25% factors of the inexperienced strains.

After which another purple line between the newly created inexperienced factors.

Then we add a purple level on the 25% level of the purple line.

And identical to that, we have computed the purpose on the 25% level of the curve.

Any further we’ll check with factors on curves by means of a `t`

worth, the place `t`

is a quantity from `0`

to `1`

. Within the above instance, the purpose can be at `t=0.25`

(25%).

t=0.25, t=0.5 and t=0.75

This manner of computing the purpose at `t`

is named De Casteljau’s algorithm and will also be used to subdivide a bezier curve. Utilizing the factors we created alongside the best way, we will additionally subdivide the bezier curve into two smaller bezier curves.

Bezier curves are fairly superb issues. Shaping the curve by adjusting the handles feels surprisingly pure, and chaining them collectively permits you to create detailed and sophisticated shapes.

And for computer systems, they’re steady and cheap to compute. For that reason they’re used for the whole lot from vector graphics to animation curves and automobile bodies.

You may see an interactive demo of bezier curves at Jason Davies’ site. It is fascinating to look at a collection of straight strains hint out a clean curve.

From https://jasondavies.com/animated-bezier

## The artistic constraints of paths

Earlier on this publish, paths have been outlined as a steady collection of strains and curves that will or could not type a loop.

The truth that paths are a single steady chain is a reasonably large limitation.

It means three manner intersections usually are not doable utilizing a single path. To create a 3 manner intersection, two or extra paths must be used. This implies coping with positioning and grouping completely different paths collectively. It additionally signifies that modifications to a single path can result in modifications to a number of different paths.

However that is merely the routine. Seasoned designers know the way paths behave, they’ll plan round it with out actually enthusiastic about it. For a static design it does not actually matter what number of paths and layers you need to create if the piece is deliberate correctly upfront.

However for some conditions the constraints that paths impose trigger numerous friction.

## Vector Networks

In 2016, Figma introduced Vector Networks. They carry the “single steady” limitation by permitting any two nodes to be joined collectively with out restrictions.

“A vector community improves on the trail mannequin by permitting strains and curves between any two factors as an alternative of requiring that all of them be part of as much as type a single chain.”

Supply: https://www.figma.com/blog/introducing-vector-networks/

The dice is the quintessential instance for demonstrating Vector Networks.

Through conventional paths, you would need to create at minimal 3 completely different paths to explain this form.

This creates numerous friction for a seemingly easy and customary form. To switch the dice, you would need to modify two or three completely different paths. However with Vector Networks you’ll be able to merely seize an edge and transfer it round, and the form behaves such as you would anticipate.

So should you would need to enhance the extrusion of the dice, you might simply seize the 2 applicable edges and transfer them collectively.

That is the massive promoting level for Figma’s Vector Networks. Ease of use.

Vector Networks do not allow you to create one thing that you just could not create with different instruments, however it does take away numerous the friction within the course of of making issues.

And you’ll take this even additional. Say you need to add a gap to the aspect of the dice.

Simply begin off by deciding on and copying the edges of the dice. You may then duplicate these edges and scale them to the scale you need them to be.

And identical to that, you have got a dice with a gap.

And to make this gap plausible, you simply want the internal edge.

Once more, Vector Networks could not mean you can create one thing you could not in any other case. As a substitute, they permit workflows that weren’t beforehand doable.

## Creating Vector Networks

With an understanding of what Vector Networks are, we will now check out how we might go about implementing them.

### Graph

The primary information construction behind Vector Networks is a graph. A graph might be regarded as a group of nodes and edges.

A graph

### Nodes

A graph could have any variety of nodes. For our functions nodes have two properties, a singular `id`

and a `place`

.

### Edges

Edges are the connection between two nodes. Every edge is a composed of two *edge components*. An edge half accommodates a node’s id and an non-obligatory management level.

The labels `n0`

and `n1`

will probably be used to check with the nodes initially and finish of an edge, respectively. The management factors will probably be labeled `cp0`

and `cp1`

.

If the management factors of the sting are omitted, the sting turns into a straight line.

## Filling the holes

Supply: https://www.figma.com/blog/introducing-vector-networks/

When working with vector networks, the Fill device permits you to “toggle” the fill of various “areas” of the graph.

These areas might be outlined as a sequence of node `id`

s that go in a circle, a loop, if you’ll.

This crazy sequence is known as a *cycle*. Within the above instance, the cycle would include the nodes `n0`

, `n1`

, `n3`

, `n4`

, `n5`

, `n6`

and `n7`

. These cycles will probably be written like `(0, 1, 3, 4, 5, 6, 7)`

.

In case you have been to depend the completely different visually distinct “areas” of the cycle your reply would most likely be three, however you might simply discover greater than three cycles.

What makes this right or incorrect?

The sequence `(0, 1, 2, 3, 4, 5, 6, 7)`

is a cycle and it loops, however it isn’t what we’re in search of. The issue might be illustrated with the “what number of triangles” puzzle you might need seen on Fb.

What number of triangles are on this picture?

You need to be capable to depend 24 completely different triangles relying on which areas you select to incorporate.

However that is not what we wish. What we have to discover are the 16 small areas.

We’d like a technique to discover the *“small cycles”* within the graph.

## Minimal cycle foundation

This paper on Minimal Cycle Basis is a bit much less dense than most others tutorial papers (and it has photos!). Its objective is:

…to compute a minimal variety of cycles that type a cycle foundation for the graph.

### What does “Minimal Cycle Foundation” imply?

It is only a fancy technique to check with the entire “small areas” of a graph. You may consider these because the “visually distinct” areas of a graph. Enclosed areas.

### Left or proper?

The primary device for locating the “minimal cycle foundation” will probably be figuring out which edge to decide on based mostly on left- or rightness.

Ought to we go to `a`

or `b`

?

We’ll consider this when it comes to clockwise and counter clockwise.

`curr`

for present, `prev`

for earlier

When touring left, we select the counter clockwise most edge (CCW) relative to the earlier one.

CCW

When touring proper, we select the clockwise most edge (CW) relative to the earlier one.

CW

### The algorithm

We will probably be discovering the minimal cycle foundation for the graph we noticed earlier.

Step one is selecting the leftmost node within the graph.

When touring from the primary node, we need to go clockwise (CW). However relative to which edge?

For the primary node, we think about that the earlier edge is “beneath” the present one. We then choose the CW edge relative to that.

On this case `a`

is extra CW relative to `prev`

than `b`

, so we’ll stroll to `a`

.

After the primary stroll, we begin selecting the CCW edge.

On this case, that is `b`

. We repeat the earlier step and preserve deciding on the CCW edge till we attain the unique node.

After we attain the unique node once more, a cycle is discovered.

We now have the primary small cycle within the graph.

When a cycle has been discovered, the primary fringe of the cycle is then faraway from the graph.

The primary edge, `(n0 n1)`

, is eliminated

Then, the *filaments* of the primary two nodes within the cycle are eliminated.

On this case, we solely have a single filament

Filaments are nodes that solely have one adjoining edge. Consider these as useless ends. When a filament is discovered, we additionally test whether or not or not the one adjoining node is a filament. This ensures that the primary node of the following cycle has two adjoining nodes. We’ll see an instance of this later.

Now we choose the primary node within the subsequent cycle. In our graph, there are two equally left most nodes.

When this occurs, we choose the underside node, `n1`

on this case.

We then repeat the method from earlier than. CW from the underside for the primary node, then CCW from the earlier node till we discover the primary node.

We discover the cycle `(1, 2, 3)`

.

Now now we have the cycles `(0, 1, 3, 4, 5, 6, 7)`

and `(1, 2, 3)`

.

Then we take away the primary fringe of the cycle and filaments like earlier than. We begin by eradicating the filaments related to the primary two nodes of the cycle.

We preserve going till there are not any filaments left.

Discovering the following cycle is fairly apparent.

CW then CCW

We now have all of the cycles of the graph.

That is the minimal cycle foundation of our graph! Now we will toggle the fills of those cycles as we please.

## The maths

I need to dig into how the clockwise-ness of an edge relative to a different edge is set.

The one prerequisite for understanding this part are vectors; arrows pointing from one level of a 2D coordinate system to a different.

i = (1, 0), j = (0, 1)

With two vectors sitting on the origin, `i`

and `j`

, we will create a sq. like so.

For the unit vectors `(1, 0)`

and `(0, 1)`

the sq. has an space of 1.

We will do that with any two vectors.

This form is named a *parallelogram*. Parallelograms have one property that we care about, which is that their space is the same as absolutely the worth of their determinant.

That will sound like jargon, however the determinant occurs to be actually helpful for us. Check out what occurs after we transfer the vectors of the one-by-one sq. nearer collectively.

When the vectors get nearer, their space will get smaller. And when the vectors are parallel, the determinant and space turn out to be 0.

At this level the pure query to ask is what occurs after we preserve going and the blue arrow is to the appropriate of the inexperienced arrow?

The determinant turns into adverse!

When the blue vector `j`

is to the left of the inexperienced vector `i`

the determinant of the parallelogram turns into adverse. When the alternative is true it turns into optimistic.

The implication for our use case is that we will test whether or not the determinant of two vectors is optimistic or adverse to find out whether or not or not a vector is to the left or proper of one other vector.

And we will do that irrespective of the course as a result of the world of a parallelogram doesn’t change relying on the orientation of the vectors that create it.

The determinant modifications when the orientation of the vectors change **relative to one another**.

With this information as our weapon, we will create a perform, `det(i, j)`

, that takes in two vectors and returns the determinant.

The perform will return a optimistic worth when `j`

is to the left (CCW) of `i`

.

### Making use of the maths

Say we’re in the midst of the discovering a cycle and we’re deciding whether or not or to not transfer to `n0`

or `n1`

.

Let’s transfer this into the coordinate system.

We’ll begin off with `a`

.

We need to get the vector from `curr`

to `a`

, which we do by subtracting `curr`

from `a`

. We’ll name this new vector `da`

.

We will do the identical for `curr`

utilizing `prev`

.

Now we will decide whether or not `a`

is left of `curr`

by computing the determinant of the parallelogram that `da`

and `dcurr`

type.

Word that the order is necessary. If we use `da`

as `i`

the world is adverse. If we use it as `j`

it turns into optimistic.

We will do the identical with `b`

.

With this info as our weapon, we all know whether or not or not `a`

, `b`

and `curr`

are left or proper of one another.

What can we do with this info?

## The inexperienced zone

We will probably be specializing in figuring out whether or not `da`

is extra CCW than `db`

relative to `curr`

. Merely put, is `da`

left of `db`

?

If `da`

is extra CCW than `db`

relative to `dcurr`

, `da`

might be stated to be *higher* than `db`

.

Step one is to find out whether or not the angle between `dcurr`

and `db`

is convex.

This “convexity” can extra simply visualized by shifting `dcurr`

again and imagining an arc like so:

The angle is convex

If the angle is convex, we we use the next expression to test whether or not `da`

is healthier than `db`

.

The ∨ image represents the logical OR operator in math.

Let’s check out the person components of this expression.

Is `da`

CCW of `dcurr`

?

Is `da`

CCW of `db`

?

I discover that it is fairly onerous to visualise this mentally, so I consider the 2 completely different expressions making a “inexperienced zone” the place `da`

is healthier than `db`

.

For the primary a part of the expression (is `da`

left of `dcurr`

), the inexperienced space seems like so.

The second a part of the expression asks if `da`

is left of `db`

. The inexperienced space look seems like so.

And because it’s an OR expression, both of those sub-expressions being true would lead to `a`

being higher than `b`

. Thus, the inexperienced space seems like this:

Is `a`

higher than `b`

?

We use this to find out the better-ness of `a`

when the angle is convex.

However what if the angle from `dcurr`

to `db`

is concave?

Then the expression seems like so:

The one factor that modified right here is that the logical OR operator (∨) modified to the logical AND operator (∧).

Let’s check out what occurs with the inexperienced zones utilizing this expression.

Is `da`

left of `dcurr`

?

Is `da`

left of `db`

?

And since these sub-expressions are joined by logical AND, the inexperienced zone seems like so:

Utilizing this technique, we will all the time get the CCW or CW most node. And the nice factor is that this technique is unbiased of rotation and actually low cost to compute.

### Computing the determinant

Given two vectors, the determinant might be computed with this system:

## Intersections within the graph

Let’s return to our graph for a bit.

This graph is the best, most optimistic case. This graph solely has straight strains, and no two strains cross one another.

This field form has an intersection. The sting `(0, 2)`

crosses the sting `(1, 3)`

.

With the intersection, the above space seems fillable. However defining the “stuffed space” is fairly tough.

What makes defining this space so tough? Take into account this rectangle and line.

There are two intersections with the sting `(4, 5)`

intersecting `(0, 1)`

and `(2, 3)`

.

Say the world left of the road is stuffed. What occurs if we transfer the road left?

Clearly the world shrinks, however what if we preserve going and transfer the road outdoors of the rectangle? Which of those outcomes beneath must be the outcome, and why?

On this case kinda feels just like the rectangle must be empty. However what if we transfer the road proper?

Ought to it then be stuffed? Certain, however what if we transfer the road up or down as an alternative? Ought to the rectangle fill or empty when the road is now not separating the 2 sections?

## Increasing the graph

That is how I consider Figma solves this downside. I name it “increasing the graph”, however the engineers at Figma most likely use a unique vocabulary to explain it.

Increasing the graph means taking every intersection, making a node on the level of the intersections, after which splitting the sides that intersected one another at that time.

That is the unique graph:

The sting `(0, 2)`

intersects the sting `(1, 3)`

. When expanded, the graph would seem like so:

A brand new node, `5`

can be added on the level of the intersection.

The perimeters `(0, 2)`

and `(1, 3)`

have been eliminated and changed by the sides `(0, 5)`

, `(5, 2)`

, `(1, 5)`

, and `(5, 3)`

.

The construction of the graph has been modified

Here is a graphic that ought to illustrate this a bit extra clearly.

Increasing an intersection

### A number of intersections

These steps are fairly easy for line edges with a single intersections. However every edge can have a number of different edges intersecting it, and two cubic bezier curves can create 9 intersections.

This complicates issues a bit. Let’s check out a bezier-line intersection.

The easiest way to go about that is to deal with the intersections for an edge as separate from the sting that intersected it.

The `t`

values go from 0 initially of the curve to 1 on the finish of it

The road has two intersections at `t = 0.3`

and `t = 0.7`

. The bezier has two intersections, however at `t = 0.25`

and `t = 0.75`

.

Earlier than we transfer on with this instance, I need to introduce a unique mind-set about edges since I consider it would assist with the general understanding of the issue.

### Duplicate edges

Two nodes could also be related a number of occasions by completely different edges.

On this graph, an edge represented by the node pair `(2, 3)`

might characterize both of the 2 edges that join `n2`

and `n3`

.

To get round this downside, we’ll give every edge a singular `id`

.

For many future examples, I’ll nonetheless check with edges by the nodes they join since I really feel it is simpler to consider. However for the following instance it is higher to separate nodes and edges.

### Intersection map

We will construction the information for the intersections of the sides like so:

Creating nodes on the intersection factors of edges

After we encounter an intersection, we create a node whose place is on the intersection. We then add intersections to an *intersection map* that may comprise the intersections for every edge with a corresponding `t`

worth and a `nodeId`

. These intersections are sorted by the `t`

worth.

For the intersection with the bottom `t`

worth, we create an edge with the primary *edge half* having the `nodeId`

of the primary edge a part of the unique edge. The second edge half ought to comprise the `nodeId`

of the intersection. This creates the sting `(2, 4)`

.

Subsequent edges could have the primary edge half’s `nodeId`

be the `nodeId`

of the earlier intersection and the second `nodeId`

be the `nodeId`

of the present intersection. On this instance, that edge is `(4, 5)`

.

One extra edge will probably be created for every edge with any intersections.

The primary edge half’s `nodeId`

would be the `nodeId`

of the final intersection and the second edge half’s `nodeId`

would be the `nodeId`

of the second edge a part of the unique edge.

This was a little bit of a mouthful, so hopefully this graphic helps a bit with understanding that alphabet soup.

Separating the intersections of an edge from the sides that created these intersections makes it simpler to consider. It alleviates among the complexity that may come up from a number of edges intersecting with one another.

## Self-intersection

Cubic beziers can self-intersect.

This, sadly, signifies that each single cubic bezier edge must be checked for self-intersection. It is an fascinating downside that entails discovering the 2 completely different `t`

values that the bezier intersects itself at, however I will not be protecting the best way to discover these values right here.

After you have the `t`

values, a self-intersecting bezier might be expanded like so:

The blue node must be invisible to the person

We insert `n3`

since having a node with an edge that has itself on each ends of the sting is problematic, however it must be hidden from the person.

Intersecting the loop of a self-intersecting bezier

Eradicating n3 on the first alternative

## Curvy edges

Earlier we lined the CW – CCW graph traversal algorithm to seek out the minimal cycle foundation (small areas).

Discovering the higher (counter clockwise most) level adjoining to `curr`

However the algorithm described within the paper was designed to work with nodes related by straight strains that do not intersect. Introducing edges outlined by cubic beziers introduces important complexity.

Which edge to decide on, blue or inexperienced?

Within the instance above, we will discover out that the blue edge is healthier than the inexperienced one through the use of the determinant. We’re stilling defining higher to imply the CCW most edge.

When working with cubic bezier curves, the naive resolution can be to simply convert the bezier to a line outlined by the factors initially and finish of the curve.

However that concept breaks down as quickly as one edge curves over the opposite.

Oops

Let’s take a contemporary take a look at a bezier curves and attempt to work from there.

Taking a look at this, we discover that the tangent initially of the curve, `n0`

, is parallel to the road from `n0`

to `cp0`

. So to get the course initially of the sting we will use the road `(n0, cp0)`

.

For readability, the beginning of our edge, `n0`

, is similar node as `curr`

.

So by changing edges outlined by cubic beziers right into a line outlined by `(n0, cp0)`

, we get the *preliminary* angle of the curve.

This looks as if a great resolution when trying on the “curve round” case.

Appears to be like like we have solved the issue. Proper?

### No intersections

Earlier than we transfer on to additional edge circumstances, it helps to know that any options assume that no two edges could intersect when deciding which edge to journey.

This isn’t allowed

The perimeters of the graph we’re traversing should not have any intersections after we compute the cycles (minimal cycle foundation) of the graph.

We will solely function on an *expanded graph*.

Like we lined earlier, an expanded graph is a graph that has changed all intersections with new nodes and edges. So if the unique, user-defined graph has any intersections, they must be expanded earlier than we will discover the graph’s cycles.

The identical edges as above, however expanded

## Parallel edges

The subsequent edge case is 2 edges being parallel (pointing in the identical course).

If the strains go in the identical course, figuring out which is healthier is unattainable with out extra info.

Listed below are just a few doable options for the circumstances the place the management factors of the curves are parallel.

### Level at `t`

What if we simply take the purpose on the curve at, for instance, `t = 0.1`

?

This produces the proper outcome for curves of an identical size, however we will simply break this with one curve being considerably greater than the opposite.

That is successfully the identical downside because the “curve round” case we noticed earlier.

### Level at size

As a substitute of taking a degree at a hard and fast `t`

worth, we might take a degree at some size alongside the curve. The size can be decided by some level on the smaller curve, e.g. at `t=0.1`

.

I’ve not tried implementing this since I’ve one other working resolution, however this might probably be a viable and performant resolution if it really works for all edge circumstances.

### Lasers!

The subsequent resolution is a bit esoteric however produces the proper outcome. That is the answer I am at the moment utilizing.

We start by splitting every bezier at `t = 0.05`

(picture above is exaggerated). We then tesselate every half into n factors.

Then, for every level of the tesselated bezier, we test whether or not a line from `n0`

to that time intersects the opposite edge.

It is fairly onerous to see what is going on on at this scale, so let’s zoom in a bit.

When a degree intersects the opposite edge, we use the purpose earlier than it.

Discovered an intersection

Let’s zoom in a bit.

The intersection shut up

For the opposite edge, now we have no intersection.

In that case, we simply use the top of the sting because the course line.

With this technique we have produced strains that appear to characterize their respective curves.

And this additionally works for the “curve round” case.

But it surely fails for a “curve behind” case.

This is able to produce the inexperienced edge because the extra CCW edge, which is flawed.

My resolution to this downside is to shoot an infinite laser within the course of the earlier edge.

We then test whether or not the factors of the tesselated bezier intersect this laser.

However a line from `n0`

to the factors would by no means intersect the laser.

Passes proper by means of

As a substitute, we will create a line from the present level to the earlier level and use that for the intersection check.

After we intersect the laser, we use the earlier level. The earlier level will all the time be on the proper aspect of the laser.

The purpose we use

And like that, now we have an answer.

## Parallel, however in reverse!

It is also the case that the blue or inexperienced edges, `a`

and `b`

respectively, could possibly be parallel to the sting from `curr`

to `prev`

.

`a`

is parallel to `prev`

The method for locating the higher edge follows a course of just like the one described above so we’ll cowl this in a short time.

There are two circumstances:

### A or B are parallel to `Prev`

, however not each

If both `a`

or `b`

, however not each, are parallel to `prev`

, we will merely examine the parallel edge to `prev`

.

If the parallel edge is CW of `prev`

, the parallel edge is healthier.

If the parallel edge is CCW of `prev`

, the opposite edge is healthier.

Suppose a bit about why that is true.

If one edge is parallel to `prev`

and curves CW, and the opposite will not be parallel to `prev`

, then the parallel edge is as CCW as might be. Which means that the inexperienced zone for the opposite edge is totally empty.

The reverse is true if the parallel edge curves CCW, since it will be as CW as doable. Which means that the inexperienced zone for the opposite edge is the entire circle.

### Each A and B are parallel to Prev

Utilizing the identical laser resolution as earlier than, this case is roofed.

## Cycles within cycles

Now we’ll take a look at fills for a bit.

Let’s check out a primary instance of a graph with a cycle within one other cycle.

You’d anticipate the graph’s areas to be outlined like so:

However because it stands, should you hover over the outer space you get a unique, unsatisfactory outcome.

However this is sensible. Let’s check out the nodes of the graph.

The cycle `(0, 1, 2, 3)`

describes the outer boundary of the world we wish, however we aren’t describing the “internal boundary” of the world but.

Let’s check out how we will do this.

### Even-odd rule

Telling a pc to attract the define of a 2D form is straightforward sufficient. However if you wish to fill that form, how do you inform the pc what’s “inside” and what’s “outdoors”?

A technique of discovering out whether or not a degree is inside a form or not is by taking pictures an infinite laser in any course from that time and counting what number of “partitions” it passes by means of.

If the laser intersects an odd variety of partitions, it is within the form. In any other case it’s outdoors of the form.

Intersects 1 wall, we’re within the form

Intersects 4 partitions, we’re outdoors of the form

This works for any 2D form, irrespective of which level you select and which course you shoot the laser in.

This additionally helps within the case of nested paths.

This provides us an concept for a way we will outline the “internal boundary” of a form.

### Decreasing closed walks

Let’s take a look at a graph with a cycle nested within one other cycle, however with an edge connecting two nodes of the cycles.

It will lead again to how we will take into consideration nested cycles and provides us a deeper understanding on how to consider them.

Let’s discover the cycles. We use the identical CW-CCW technique as ordinary.

With this technique, we go on what seems like a small detour across the internal cycle.

After we attain the node we began at, that is what the cycle seems like.

That is the primary cycle we have seen the place we cross a node twice (each `n3`

and `n4`

). One thing fascinating seems after we check out the course that the cycle takes all through the graph.

We begin off touring CCW, however after we cross the sting from the outer cycle to the internal cycle the orientation we journey appears to flip.

I’ll state for now that we need to separate the outer cycle from the internal cycle and deal with the sting between them as if it did not exist. I’ll go into the *why* later and clarify the *how* right here.

We take all repeated nodes, on this case `n3`

, and take away them from the cycle. We additionally take away any nodes which are between the 2 repeated nodes.

You may discover that `n4`

can be repeated, however because it’s “inside” of the a part of the cycle that `n3`

removes, we will ignore it.

We depart one occasion of the repeated node, after which now we have the cycle that might have been discovered if the *crossing* did not exist.

We then mark the sting that related the outer cycle from the internal cycle. I name these marked edges *crossings*.

It is also the case that an outer-inner cycle combo has a number of crossings.

In that case, we mark all edges adjoining to the node related to the outer cycle as a crossing.

And in any case that is completed, our cycles seem like so:

## Subcycles

As a substitute of referring to “internal” and “outer” cycles, I’ll check with subcycles and father or mother cycles. It will make it simpler to consider a number of cycles relative to one another.

Having stated that, let’s introduce a 3rd cycle.

After we hover over the outermost cycle, what do you anticipate to occur?

Due to the even-odd rule, the innermost cycle is stuffed too!

To repair this, we will introduce the idea of *direct subcycles*.

Mum or dad cycles (blue) and their direct subcycles (inexperienced)

A father or mother cycle could have a number of direct subcycles. However as a result of non-intersection rule, a subcycle could solely have a single father or mother cycle.

Let’s check out how this works.

This graph has a a rectangle, our outermost cycle, which has two direct subcycles: a diamond and an hourglass. The diamond has two direct subcycles of its personal, and the hourglass has three direct subcycles.

We are going to start with the rectangle and its direct subcycles. We are going to title them, `c0`

, `c1`

and `c2`

.

The person has determined to fill a few of these cycles, and depart a few of them empty.

`c0`

and `c1`

are stuffed, and `c2`

is empty

Let’s draw the graph with out a stroke and with a grey fill. When drawing this graph we begin with the outermost cycle, `c0`

.

The graph to the left with the render to the appropriate

Since `c0`

is stuffed, we draw it. If it weren’t stuffed we might skip drawing it. We will shoot a laser out of the rectangle and see that it intersects the partitions of the rectangle as soon as, so we will anticipate it to be stuffed contemplating the even-odd rule.

This will appear actually apparent, however it’s good to have the principles of the sport laid out clearly earlier than we transfer on.

Subsequent we need to draw `c1`

, the diamond in our graph. It was stuffed, identical to the rectangle so we should always draw it as effectively. But when we strive to attract the diamond as effectively, we get the flawed outcome.

Our laser is intersecting two partitions on account of drawing each of the shapes when the have the identical fill setting.

We intersect a fair variety of partitions, so we’re “outdoors” of the form

So to attract the picture the person needed we will merely skip drawing the diamond for the reason that father or mother cycle implicitly attracts direct subcycles with the identical fill setting.

The hourglass, `c2`

, is meant to be empty. With that being the case, simply not drawing it looks as if an inexpensive conclusion. However for the reason that father or mother cycle (rectangle) has already drawn the hourglass as if it have been stuffed we have to “flip” the fill by drawing the hourglass.

And once more, if we attempt to use the laser intersection technique we see that the variety of intersections is 2, a fair quantity. And with the even-odd rule, a fair variety of partitions means you are “outdoors” of the form.

Now that we have drawn the rectangle and its direct subcycles, we will transfer onto the direct subcycles of these.

When working with `c3`

and `c4`

, the direct subcycles of `c1`

, we will deal with them as in the event that they’re direct subcycles of `c0`

since `c1`

had the identical fill setting.

For `c3`

, we need to “flip” the fill setting so we draw it. However `c4`

has the identical fill setting as its father or mother cycle so we do not draw it.

Even variety of intersections so we’re outdoors of the form

And we will consider `c5`

, `c6`

and `c7`

in the identical manner. We do not care whether or not they’re stuffed or empty when rendering them. We care whether or not or not they’ve the identical fill as their father or mother cycle.

We solely want to attract cycles if their father or mother cycle has the alternative “fill setting” as themselves. If they’ve the identical fill setting, we do not have to attract them.

Which means that when drawing cycles, begin by drawing the outermost “stuffed” cycle after which take a look at that cycle’s subcycles. If a subcycle has the identical fill setting as its father or mother cycle, it shouldn’t be drawn.

## Contiguous cycles

A graph could have a number of “clusters” of cycles.

I exploit the phrase *contiguous cycles* to explain the “togetherness” of the cycles, if you’ll. I typically consider these contiguous teams of cycles as being in numerous colours.

Discovering these contiguous cycles might be completed with a depth-first traversal:

Begin on the first node of the cycle

Shade every node you discover

However keep in mind the *crossings*? Within the search, chances are you’ll not crawl to adjoining nodes by edges marked as crossings.

So in the long run, our colours really seem like this:

Take this group of contiguous cycles nested inside one other group of contiguous cycles:

Due to the non-intersection rule we all know that if one of many nodes in a gaggle of contiguous cycles is within a cycle not within the group, all of them are.

This “contiguous cycles” concept is perhaps not essentially the most fascinating a part of this publish on the floor, however I’ve discovered it to be helpful when engaged on Vector Networks.

## Partial growth

When hovering an space outlined by intersections, we’re displaying a cycle of the expanded graph.

Take this triangle for example.

If we hover over one in every of its areas, we see an space outlined by nodes that do not exist but.

What the blue striped space represents is the world whose fill state can be “toggled” if the person clicks the left mouse button. This space doesn’t exist on the graph because the person outlined it. It exists as a cycle on the expanded model of the unique graph.

The expanded graph

When the person clicks to toggle the fill state of the world, we might first must develop the graph for the nodes and edges that make up that space to exist.

The expanded graph

However by doing that we have expanded two intersections that we did not have to develop to have the ability to describe the world. These expansions are harmful in nature and must be prevented when doable.

As a substitute, we will *partially develop* the graph by solely increasing the intersections that outline the chosen cycle.

Partially expanded graph

This permits us to take care of as a lot of the unique graph as doable whereas nonetheless having the ability to outline the fill.

### Implementing partial expansions

The fundamental implementation is fairly easy. If you create the expanded graph, simply add somewhat metadata to every expanded node that tells you which ones two edges of the unique graph have been used to create it and at what `t`

values these intersections occurred.

Then when the cycle is clicked, iterate over every node. If the node exists within the expanded graph however not the unique graph, add it to a brand new partially expanded graph.

There are edge circumstances, however I can’t be protecting them right here.

## Omitted subjects

Listed below are among the subjects that I made a decision to omit for this publish. Go have a stab at them your self!

### Joins

Figma presents three kinds of joins. Spherical, pointy and sq.. How might these various kinds of joins be carried out?

### Stroke align

Figma additionally presents 3 ways to align the stroke of a graph: middle, inside and out of doors.

How do you establish inside- or outside-ness and what occurs when the graph has no cycles?

### Boolean operations

Figma, like most vector graphics instruments, presents boolean operations. How might these be carried out?

Paper.js is open supply and has boolean operations for paths, perhaps you can begin there?

## Future subjects

These are among the extra open-ended options and concepts I need to discover sooner or later.

### A unique manner of working with fills

There are options to how Figma permits the person to work with fills.

One doable resolution I am focused on exploring is a number of completely different “fill layers” that use one vector object as a reference. This is able to remedy the “one graph, multiple colors” downside with out having to duplicate the layer and preserve a number of vector objects in sync if you wish to make modifications afterward.

### Animating the graph

Given an expression and reference based mostly system just like After Results, what might you obtain while you mix it with Vector Networks?

Or perhaps we might make use of a node editor just like Blender’s shader editor or Fusion’s node based workflow?

There’s numerous exploration to be completed right here and I am actually excited to dive into this matter.

## In closing

Thanks for studying this publish! I hope it served as a great introduction to what I believe is a very fascinating downside house. I have been engaged on this downside alongside faculty and work for a great whereas. It is a part of an animation editor plus runtime for the net I am engaged on. I intend for a modified model of Vector Networks to be the core of some options.

I have been engaged on implementing Vector Networks for a bit over half a 12 months now. The vector editor is fairly sturdy in the case of creating, modifying and increasing the graph. However the edge circumstances when modifying the fill state have been stumping me for fairly some time now.

I needed to have a totally working demo earlier than publishing this publish, however it is going to be just a few months till it is steady sufficient for it to be usable for folks that aren’t me.

The massive concept behind the venture is to be a bit of animation software program that is tailored for creating and working dynamic animations on the internet. I am going to share extra about this venture at a later date.

I additionally simply assume that Figma’s Vector Networks are tremendous cool and it is actually onerous to seek out materials about it on-line. I hope this publish helps repair the ignorance that I encountered when searching for details about Vector Networks.