Loading editor...

5. Barycentric coordinates

A 2-by-2 matrix maps a point to a point, and a matrix-vector product is a weighted sum of the columns of the matrix. If the matrix has rank 2, its action can be reversed: the inverse matrix sends every output point back to its input point.

In this chapter we use these tools to solve a concrete geometric problem - determining whether a point is inside or outside a triangle.


5.1 How to determine if a point is inside or outside a triangle?

Given a triangle on a 2d-plane with vertices [x1,y1], [x2,y2] and [x3,y3], and given a point [x,y], how can you find out whether the point is inside or outside the triangle? Before we address how to solve this problem, think about how you would approach it for a minute or two.

This problem seemingly has no connection with linear algebra but, as we will see below, can be solved elegantly with the tools of linear algebra that we have learnt so far.

5.1.1 Let's start with an example

Let's say the vertices of are , and . And we want to know whether or not [2,1] is inside the triangle.

We will also keep implementing a function to do the same as we make progress:

def is_point_in_triangle(vertices, point):
    '''
    vertices: [[x1,y1],[x2,y2],[x3,y3]]
    point: [p,q]
    '''
    ...

5.2 Move (or translate) the triangle and the point to a better location

The canvas shows the same triangle and point again. Now consider a second copy of the problem where one of the vertices of the triangle ([3,0]) lies on the origin, and everything else keeps its position relative to that vertex: .

These two problems are actually the same problem - [-3,0], so the position of the point relative to the vertices of the triangle is unchanged. But it is easier to find the answer of the second problem, which is also the answer of the first problem. So we will now focus on how to solve the second problem: is the point [-1,1] inside the triangle with vertices [0,0], [-1,3] and [-7,-2]?

5.2.1 How to go from problem on the left to problem on the right?

This is easy: if we want to move the vertex [x1,y1] to the origin, we subtract [x1,y1] from all three vertices and the point.

def is_point_in_triangle(vertices, point):
    '''
    vertices: [[x1,y1],[x2,y2],[x3,y3]]
    point: [p,q]
    '''
    x1,y1 = vertices[0]
    x2,y2 = vertices[1]
    x3,y3 = vertices[2]
    shifted_vertices = [[x1-x1,y1-y1],[x2-x1,y2-y1],[x3-x1,y3-y1]]
    shifted_point = [point[0]-x1, point[1]-y1]

5.3 First change in viewpoint: look at sides of the triangle as vectors

Let's name the vertices and the point first: A = [0,0], B = [-1,3], C = [-7,-2] and P = [-1,1].

We want to look at the sides of the triangle AB, AC and the point P as vectors. The side AB can be seen as in the 2-dimensional space. Similarly AC can be seen as . is the vector .

Next we would like to get to the point P by taking weighted sums of the vectors AB and AC.


5.3.1 Let's walk in the 2d space by taking weighted sums of these two vectors

Remember that this walk can be represented algebraically as a matrix-vector multiplication where the matrix has the vectors AB and AC as its columns, and the vector is the weights for each column:

On the canvas, is times the vector AB, is times the vector AC placed tip-to-tail, and lands on the weighted sum.

We want to observe for what values of the output point lies inside the triangle. It is this insight that leads to the solution. Before we reveal the pattern for the weights, let's play with it a bit and get a feel for it:

  • ·
  • ·

Let's also write a function for matrix multiplication:

def scale_vector(scale, vector):
    return [i*scale for i in vector]

def add_vectors(array_of_vectors):
    dimensionality = len(array_of_vectors[0])
    output = [0]*dimensionality
    for vector in array_of_vectors:
        output = [i+j for (i,j) in zip(output, vector)]
    return output

def matMul(columns_of_matrix, weights):
    scaled_columns = [scale_vector(scale, vector) 
        for (scale, vector) in zip(weights, columns_of_matrix)]
    return add_vectors(scaled_columns)

5.3.2 A glimpse of the solution

Here are some observations about the output point of the matrix multiplication above:

The output point lies inside the triangle only when , and .

  • When , the input vector (which is at the origin).
  • When , the input vector .
  • When , the input vector .

Now the question is: if we want to find out whether the point lies inside the triangle or not, we need to find out for which values of we have


5.4 The final solution

This brings us to the final solution. We know that the matrix maps to . Then the matrix that maps back to is the inverse of .

We saw how to invert a 2-by-2 matrix in the last chapter. This is how a function to do so looks like:

def get_inverse_of(matrix2d):
    x1,y1 = matrix2d[0]
    x2,y2 = matrix2d[1]
    scale = 1/(x1*y2 - x2*y1)
    return [[y2*scale, -y1*scale],[-x2*scale, x1*scale]]

Let's see what the inverse of this matrix is:

It is important to understand this part:

Multiplying this inverse matrix with a vector gives us the weights for which the weighted sum of the vectors AB and AC equals .

Now if these weights follow these three rules, it means that the point lies inside the triangle:

Let's write a function to check for these conditions:

def weights_are_valid(w1,w2):
    return w1 >= 0 and w2 >= 0 and w1+w2 <= 1

Note that in this example we have

Thus and . Since these values satisfy the three conditions above, the point [-1,1] indeed lies inside the triangle. You can verify this on the canvas: and watch the output arrow land exactly on the gold point P.

Below is a full implementation of a function that takes as input vertices of a triangle and a point. It returns True if the point is inside the triangle and False otherwise.

def is_point_in_triangle(vertices, point):
    '''
    vertices: [[x1,y1],[x2,y2],[x3,y3]]
    point: [p,q]
    '''
    x1,y1 = vertices[0]
    x2,y2 = vertices[1]
    x3,y3 = vertices[2]
    shifted_vertices = [[x1-x1,y1-y1],[x2-x1,y2-y1],[x3-x1,y3-y1]]
    shifted_point = [point[0]-x1, point[1]-y1]
    
    # create a matrix with sides AB and AC as vectors
    matrix = [shifted_vertices[1], shifted_vertices[2]]
    inverse_matrix = get_inverse_of(matrix)
    # get the weights needed to scale the vectors AB and AC
    w1, w2 = matMul(inverse_matrix, shifted_point)
    return weights_are_valid(w1,w2)

5.4.1 Extending the idea to polygons

Once you figure out how to determine whether or not a point is inside a given triangle, you can easily extend this idea to polygons. All you need to do is to break down the polygon into a set of triangles. Then a given point is inside the polygon if it is inside any of these triangles.

For example, take with vertices [-1,-2], [3,-2], [4,3], [-3,2], [-4,0]. We can by drawing diagonals from one vertex. Now the question "is the point inside the pentagon?" becomes "is the point inside any of these three triangles?"

This is what a function to do so would look like:

def is_point_in_polygon(vertices, point):
    triangles = decompose_polygon_into_triangles(vertices)
    for triangle in triangles:
        if is_point_in_triangle(triangle, point):
            return True
    return False

How will you implement the function decompose_polygon_into_triangles? Does the function work for all polygons? What if a polygon is not convex?

In the next chapter, we will look at what it means to multiply a matrix with another matrix.


← 4. Rank and reversibility · 6. Matrix-matrix multiplication →