Page 17: Polygons: Inside and Out

Spring 2026 Sample Solution

Polygons are an important primitive for graphics. They may seem simple, but they aren’t always. We’ll start with some basic definitions (convex, concave, handedness (clockwise), simple, non-simple) and then learn about the tricky business of deterimining what is inside and outside so we can fill (color them in) correctly.

Understanding polygon filling is useful because it allows us to use 2D graphics APIs to make complex shapes. I want you to understand why this is so useful.

At the end of this page there are practice questions to help you check that you understand. If you want know the material you can skip to Practice .

If you want to see lecture videos on this subject check out:

and

The Basics

A polygon is defined by a list of points, that we sometimes call its vertices. Lists are, by definition, ordered.

A polyline is the shape that is defined by the lines connecting the vertices (in order). It is “thin” (like a line) - although when we draw it, we need to give it some thickness (even though mathematically it is infinitely thin). It has no area. It might be open or closed. A closed polyline connects from it’s last point to its first point (generally, this is done by copying the first point at the end).

A polygon is the shape that is bounded by a set of vertices. It must be closed (so it bounds some area). Generally, the last point connects to the first (but this can actually get complicated). Polygons are the “area” inside them - although that means we need to define what inside and outside means. This can be simple (for “simple” polygons) - but can get complicated, which is why we need this page to explain it.

If you want to be technical about it, the blue area is the polygon, the black line (that goes around it) is the polyline.

The term polygon usually refers to shapes made with straight lines between the vertices. If we allow curves between the points, we call it a curvalinear polygon, or a polycurve.

The 2D APIs we’ll learn about (Canvas and SVG) are happy to allow you to connect the vertices with curves.

The simplicity of triangles makes them quite useful - especially when we get to 3D. In fact, in many 3D systems, everything will be made of triangles. Triangles have the advantage that they are always in a plane (they are flat). In 2D, all their points are in the plane of 2D. In higher dimensions, the 3 points define a plane.

It is important to note that triangles (as all polygons) have ordering. Remember that the points are a list, and therefore ordered. We might not see the ordering after we draw the triangle, but the order of its points may matter sometimes (we’ll see some examples below). Ordering will turn out to be important in 3D as well.

For a triangle, we might have the vertices go clockwise or counter-clockwise.

One last point to make with triangles: so far, we’ve been assuming that the three points are distinct. If two of the points are in the same place, or if all three are co-linear, the triangle collapses to a line. It has no “area” inside of it. If all three points are in the same place, the triangle collapses to a point.

A Four Sided Polygon

A four-sided polygon (or four vertex polygon) is sometimes called a quadrilateral, or quad for short. It has a few more options for what can happen. Here are a few quads to talk about:

The left (blue) one is a square. The middle one (green) moves one of the points, and the right most one has the same point positions as the square but re-orders them.

A convex shape (like the square) is one that if you connext any two points inside the shape with a line, the line is within the polygon. A concave shape (like the green thing) is one that has an indentation (it is not convex). You can find a pair of points inside where the line connected them goes outside. The green polygon is concave. Notice how the red line starts and ends inside the polygon, but goes outside of the polygon.

An important concept in geometry is the Convex Hull. The Convex Hull of a set of points (in 2D) is the smallest convex polygon that encloses that set of points. You can find it by wrapping a string around the points (or by algorithms you can learn in a different class). So, for the green polygon it is the triangle that surrounds all of the points. We’ll see more examples in a bit…

The red quad above looks like two triangles. In fact, we could draw it by drawing two separate triangles - however in order to do that, we would have to introduce a fifth point where the two lines cross. Notice on the right we now have two triangles (0,1,2) and (0,3,4).

This four sided polygon is an example of a non-simple polygon. A simple polygon is one that is a single chain of points whose edges do not cross. Technically, there are a few other criteria - but these are the important ones for us.

To show a disconnected polygon, I’ll need to make hexagons. On the left is a simple hexagon. On the right, it might look like 2 triangles, but it really is a single, 6-sided polygon that is not simple.

Notice that there is no edge between vertices 2 and 3 in the red polygon. We can do this in the canvas API with (simplified code - without the arrow and circles):

1
2
3
4
5
6
7
ctx.moveTo(25, 75);
ctx.lineTo(50, 25);
ctx.lineTo(75, 75);
ctx.moveTo(110, 75);
ctx.lineTo(135, 25);
ctx.lineTo(160, 75);
ctx.fill();

I am hoping your first question here is “why?” That will be answered in the next section. But first a note on “how?”:

Notice that the “path” of the pen is discontinuous. There is a second moveTo command on 4. This makes a disconnected path - which is how we make non-simple polygons.

If we are filling polygons, we do not need to use closePath - there is an implication that the last point connects to the beginning. However, if we are drawing a stroke around the polygon, closing the path is important.

closePath works to close the current chain. It connects the last point to the most recent moveTo.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ctx.fillStyle = "#CCC";
ctx.lineWidth = 3;
ctx.moveTo(25, 75);
ctx.lineTo(50, 25);
ctx.lineTo(75, 75);
// ctx.closePath();
ctx.moveTo(110, 75);
ctx.lineTo(135, 25);
ctx.lineTo(160, 75);
ctx.closePath();
ctx.fill();
ctx.stroke();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ctx.fillStyle = "#CCC";
ctx.lineWidth = 3;
ctx.moveTo(25, 75);
ctx.lineTo(50, 25);
ctx.lineTo(75, 75);
ctx.closePath();
ctx.moveTo(110, 75);
ctx.lineTo(135, 25);
ctx.lineTo(160, 75);
ctx.closePath();
ctx.fill();
ctx.stroke();
Aside: How does the AI define a simple polygon?

I point this out - because it gets at the fact that there are technically more distinctions that don’t really matter for us. The 2D APIs we will use can handle non-simple polygons.

Prompt to Gemini: what is the definition of a simple polygon?

In geometry and computer graphics, a simple polygon is a flat shape consisting of straight line segments that form a closed loop and do not intersect themselves.

To be strictly defined as “simple,” the polygon must meet these criteria:

  1. Enclosure: The segments form a single closed boundary.
  2. No Crossing: Edges do not cross or touch each other, except at the vertices where consecutive segments meet.
  3. Vertex Sharing: Exactly two edges meet at every vertex.
  4. No Holes: The shape encloses a single, contiguous interior region.

Why it matters in CS/Graphics

The distinction is critical because many standard algorithms - such as triangulation, scan-line filling, or point-in-polygon tests - rely on the assumption that the polygon is simple.

If a polygon intersects itself (like a pentagram or a bowtie shape), it is called a complex or self-intersecting polygon. These often break standard rendering algorithms or require more complex rules (like the Even-Odd rule or Non-Zero Winding rule) to determine what counts as the “interior.”

Source: https://en.wikipedia.org/wiki/Simple_polygon

The Joy of Non-Simple Polygons

Let’s say I wanted to make a triangle with a hole in it… A real hole (so you can see what was drawn before it - behind it, if you will).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const p0 = [25,175],  i0 = [65,150];
const p1 = [100,25],  i1 = [100,81];
const p2 = [175,175], i2 = [135,150];
ctx.moveTo(...p0);
ctx.lineTo(...p1);
ctx.lineTo(...p2);
ctx.moveTo(...i0);
ctx.lineTo(...i2);
ctx.lineTo(...i1);
ctx.fill();
ctx.stroke();
ctx.fill();
The ... (spread) notation

In the example, I used three dots. This is called “spread notation” in Javascript. When used in the parameters of a function call, it “spreads” an array out into individual arguments.

So, moveTo(...[25,175]) is the same as moveTo(25,175).

To convince you that it really is a hole (and not just a white triangle in the center…) Let me do that again with something in the background.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
ctx.strokeStyle = "blue"; ctx.lineWidth=1;
for(let y=10; y<200; y+=10) {
    ctx.moveTo(0,y); ctx.lineTo(200,y);
    ctx.moveTo(y,0); ctx.lineTo(y,200);
}
ctx.stroke();
ctx.beginPath();
// the triangle
const p0 = [25,175],  i0 = [65,150];
const p1 = [100,25],  i1 = [100,81];
const p2 = [175,175], i2 = [135,150];
ctx.moveTo(...p0);
ctx.lineTo(...p1);
ctx.lineTo(...p2);
ctx.moveTo(...i0);
ctx.lineTo(...i2);
ctx.lineTo(...i1);
ctx.fill();
ctx.stroke();
ctx.fill();

Of course, you could have made this from 4 quads (or a whole bunch of triangles).

But making shapes by adding and subtracting is sometimes easier. I should point out here that if I draw the edges of the polygons, we will see how things are made. If I was just filling the areas, we wouldn’t see the structure.

Here’s another example… Let us try to make a 5 pointed star. This is a pentagram (a 10 sided figure) not a pentagon (I’ll draw one of those too).

Figuring out those 10 points is a lot harder than just connecting the corners of the pentagon (they way I was taught to draw a star as a kid). What if we could just put the pentagon points in a different order?

Unfortunately, we might not get what we want… Or we might, depending on how we define what is inside and outside of the polygon.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const pp = [ [100.00, 25.00], 
             [171.33, 76.82], 
             [144.08, 160.68], 
             [55.92, 160.68], 
             [28.67, 76.82] ];
ctx.beginPath();
ctx.moveTo(...pp[0]);
ctx.lineTo(...pp[2]);
ctx.lineTo(...pp[4]);
ctx.lineTo(...pp[1]);
ctx.lineTo(...pp[3]);
ctx.fill("evenodd");
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const pp = [ [100.00, 25.00], 
             [171.33, 76.82], 
             [144.08, 160.68], 
             [55.92, 160.68], 
             [28.67, 76.82] ];
ctx.beginPath();
ctx.moveTo(...pp[0]);
ctx.lineTo(...pp[2]);
ctx.lineTo(...pp[4]);
ctx.lineTo(...pp[1]);
ctx.lineTo(...pp[3]);
ctx.fill("nonzero");
The only difference between these two is the “rule” used to determine what is inside (or outside) of the polygon. The same rule that lets us cut holes in polygons becomes a problem when we want to make different things. Therefore, we need to understand the rules used to define what is inside and outside of the polygon.

Inside-Outside Rules 1: Even/Odd

For simple polygons, the definition of inside is simple.

There are several different ways to define what is “inside” of a non-simple polygon. The two APIs we will use in class both support two different rules: the “even/odd” rule and the “nonzero” (winding) rule.

The Even/Odd rule: When you want to decide whether a point is inside, cast a ray (start drawing an infinite straight line from the point) in any direction. Count the number of times the line crosses the edges of the polygon. If the number of crossings is odd, then the point is inside the polygon. If it is even, it is outside.

Here’s an example using the 6-sided figure (triangle with a hole) from before. Here, it’s the same point in each case - just different directions for the ray. (any direction works - fun math fact). Notice how in all cases, the red “rays” cross an odd number of edges.

On the left (the horizontal ray) crosses 3 edges. In the middle, the vertical ray crosses 1 edge. It’s always odd. This point is inside.

For a point outside, the story is different…

Notice that the ray always crosses an even number of edges - it might be 0, 2 or 4 - but it is always even.

For the case of the hole, points in it are always even as well.

Now we can see what was going on with the pentagram. In the arms, odd.

But, in the center, even!
I made the line go downward to emphasize that it still is crossing two edges: it is just crossing two edges in the same place.

If we want to make shapes like this, we need a different rule for what is inside and outside.

Inside Out Rules 2: Non-Zero Winding

While the even/odd rule is simple, it doesn’t give us enough control to either add or subtract shapes. So we often prefer more complicated “winding” rules. We will only look at a specific winding rule: non-zero winding. This is provided in both Canvas and SVG. It is the default in both.

The big difference with winding rules is that they consider the direction that the edges are going, so the order of the vertices matters. The basic idea is quite similar to even/odd (so make sure you understand that first). But it has two important differences.

The Non-Zero Winding Rule: When you want to decide whether a point is inside, cast a ray (start drawing an infinite straight line from the point) in any direction. Count the number of times the line crosses the edges of the polygon. Count +1 if the edge goes from left to right as it crosses the ray. Count -1 if the edge goes from right to left. If the sum is not zero, the point is inside the polygon, if it is zero, it is outside.

A Historical Aside... Winding

When I was taught this, it was phrased in terms of positive and negative loops around the point. If the loop went around the point clockwise, add one; counter-clockwise subtract one. Trying to figure out loops is much harder than counting directional crossings. I believe it is equivalent.

By the way… some descriptions flip the directions - but since we only care about zero or not zero, it doesn’t matter what the sign of the count is.

The key here is that we consider the direction of the edge (remember when we talked about ordering back at the beginning?). If we are “facing” the direction of the ray, if the edge goes from left to right, we add 1. If it goes from right to left, we add -1.

Here are two examples. Notice that I need to show the directions of the edges for this to make sense.

As we move along the line, we first cross the edge from 4 to 5 which is going right to left (relative to the ray), so we add -1. Then we cross the edge from 1 to 2 which is going left to right so we add +1. They add up to zero, so this is outside of the polygon.

As with even-odd, we can pick any direction. So we can try this differently…

Here, the first edge we encounter is 3 to 4 which is going left to right on the page, but is right to left relative to the ray, so we add -1. Then we encounter the edge 2 to 0, which is going left to right (relative to the ray), so we add +1. This adds to zero. Outside.

As long as we’re consistent in how we count, it doesn’t matter how we define the directions. Edges in different directions have to count opposite. We can actually choose directions relative to the screen rather than the ray, or flip +1 and -1. The important thing: the relative directions of the different crossings matter.

For points “inside” we will end up with non-zero counts…

For the ray going upwards, 3-4 is left to right +1, 4-5 is right to left -1, 1-2 is left to right so +1. It sums to one. Going downward, it crosses one edge 2-3. This is left to right if you are following the ray, or right to left on the screen - you get 1 or -1, which is not zero either way.

Now an example that shows why this is useful. We can reverse the order and control what happens. In this example, notice how the right example has the inner triangle going clockwise - the same direction as the outer triangle.

On the left, as before we add -1 (for 4-5 goint right to left) and 1 (for 1 to 2 going left to right). On the right, we add +1 (for 4 to 5 going left to right) and +1 (for 1-2 going left to right), which is 2 - not zero, so the center point is inside (so it is filled).

And now we can understand what was happening in the pentagram example (with the even odd rule) from before…

For a point in the center, most rays cross 2 edges. But those edges are going in the same direction (in the example of the vertical ray, it crosses 0-1 and 2-3, both of which are going left to right). For even odd, we cross 2 edges, so we get a total of 2 - which is even, so it is outside. For nonzero, we cross two left to right edges, so we also get a total of 2, which is not zero, so it is inside.

How to choose fill rules?

The APIs we use for 2D graphics (Canvas and SVG) give you the choice of using the even/odd fill rule or the non-zero winding fill rule. In both cases, non-zero is the default. Most of the time this doesn’t matter: the fill rules only give different results for non-simple polygons.

Which one should you use?

  • The even/odd rule is simpler: you don’t need to worry about what order the vertices are in.
  • The non-zero rule gives you more control: you can cut holes (or overlap shapes) easily by being careful about what direction your polygon is in.

In general, it is usually best to use non-zero and to be careful about what direction the polygons go in. Have your polygons go one direction (e.g. counter-clockwise), and the “holes” go counter-clockwise.

More Examples

I was wondering if there were other shapes like a pentagram, but with more sides. Here are 7-vertex versions (septagrams?). You can connect every 2nd vertex (the same as a pentagram), or 4th to get different shapes. Notice how we get more internal edge crossings. With the non-zero rule, the whole thing gets filled. With the even odd-rule, we see some interesting structure.

We can use this to make a 9 sided star as well. The non-zero rule fills it simply, while even/odd shows some interesting patterns.

Here is an example of a simple figure that crosses over itself. We often make contrived objects like this to check how well students understand filling rules. In this case, the even odd rule allows the shape to have a hole.

Practice

You should be able to predict what is inside and what is outside with either filling rule.

Make your guess using the drop downs before looking at the answer (in the expand box).

This only has a hole with the even-odd rule (the hole has 2 crossings), and it is C shaped.

Decide inside or out and press 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const path = [25,25, 100,25,    
              100,175, 50,175, // 2-3
              50,50, 175,50,   // 4-5
              175,100, 75,100, 
              75,150,  175,150, 
              175,200, 25,200 
 ];
ctx.fillText("Non-Zero Winding",50,215);
poly.drawPolygon(ctx,path,{vertexRadius:0, arrowSize:0});
ctx.fillText("Even-Odd",290,215);
poly.drawPolygon(ctx,path,{shiftX:200, fillRule:"evenodd",vertexRadius:0, arrowSize:0});

And a reminder of making holes… For each of the fill fules, which of the triangles will be a hole?

Make your guess using the drop downs before looking at the answer.

With the non-zero rule, only the right triangle is visible (since it has the opposite orientation as the outer rectangle). With the even-odd rule, both triangles are visible.

Decide inside or out and press 
1
2
3
4
5
6
7
8
9
const path = [
    25,25, 225,25, 225,175, 25,175, "*",
    50,150, 75,50, 100,150, "*",
    200,150, 175,50, 150,150
 ];
ctx.fillText("Non-Zero Winding",100,190);
poly.drawPolygon(ctx,path,{vertexRadius:0, arrowSize:0, lineWidth:0});
ctx.fillText("Even-Odd",350,190);
poly.drawPolygon(ctx,path,{shiftX:250, fillRule:"evenodd",vertexRadius:0, arrowSize:0, lineWidth:0});

And one more puzzle to make sure you understand this…

There are two overlapping regions - one with a red dot, and one with a green dot.

Decide inside or out and press 
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
const path = [
     25,25, 100,25, 100,100,  25,100, "*",
     60,70, 190,70, 190,150, 60,150, "*",
    225,25, 150,25, 150,100, 225,100
 ];
ctx.fillText("Non-Zero Winding",90,180);
poly.drawPolygon(ctx,path,{vertexRadius:0, arrowSize:0, lineWidth:0});
ctx.fillText("Even-Odd",350,180);
poly.drawPolygon(ctx,path,{vertexRadius:0, arrowSize:0, lineWidth:0,fillRule:"evenodd",shiftX:250});
ctx.beginPath();
ctx.fillStyle="red";
ctx.arc(80,85,8,0,2*Math.PI);
ctx.arc(330,85,8,0,2*Math.PI);
ctx.fill();
ctx.beginPath();
ctx.fillStyle="green";
ctx.arc(170,85,8,0,2*Math.PI);
ctx.arc(420,85,8,0,2*Math.PI);
ctx.fill();

That is our lesson on Polygons.

Drawing with Canvas

We’ve covered the basics of the Canvas API. We haven’t covered curves or transformations, because we’ll learn about them later in the course when we learn the math behind them. We also didn’t discuss some of the alternative methods for defining paths. You can learn about those in the documentation.

If you would like more resources, here are some good ones (all optional):

  1. Mozilla (Official) Canvas API Documentation This is the “official” documentation. Everything is in here, somewhere. It is actually quite well written and well organized.
  2. Mozilla (Official) Canvas Tutorial (top level) This is part of #1. It very quickly gets beyond the basics. We mainly need the basics. Many of the pages are very useful such as Mozilla (Official) Canvas Tutorial: Drawing Shapes and Mozilla (Official) Canvas Tutorial: Styles.
  3. Canvas Cheat Sheet: A concise page that reminds you of the different things you can do with Canvas.
  4. HTML Canvas Deep Dive: This is a “book length” tutorial on web graphics program (it even gets to 3D stuff). The first chapter covers a lot of the basic stuff.
Next: Page  18 - Where did I draw

Now is probably a good time to write out your localstore to JSON and to make a checkpoint, here are the panels:

Next: Page  18 - Where did I draw