Processing math: 100%

Lecture 9: Triangles

For spheres and planes, we plugged the ray equation p(t)=po+td into the equation of the shape in question to solve for intersection. But what is the equation for a triangle? We have the plane equation, and we have line equations for each edge. We could find the plane intersection point, then check against each line. In fact, this is how you can support arbitrary polygons, but there are more convenient representations for triangles.

Review: Barycentric Coordinates

Barycentric coordinates of point p in terms of a, b, and c are the numbers α, β, and γ such that:

p=αa+βb+γc

With the constraint:

α+β+γ=1

This must be true if the three values are barycentric coordinates.

However, the values don’t have to be positive.

A point p is inside the the triangle if and only if:

0α1 0β1 0γ1

What does α=0 mean? Any point opposite the vertex a, or rather along the line from b to c.

What about α,β=0? In this case we know γ must be 1, this is the c vertex.

The center of the triangle is α=β=γ=13

One interesting thing to note is that we’re really defining a coordinate system with, e.g., origin a and basis vectors ba and ca

p=a+β(ba)+γ(ca)

Intersection

Let’s plug in our standard ray equation:

P0+td=a+β(ba)+γ(ca)

Since this is a vector form it is really three different equations:

P0x+txd=xa+β(xbxa)+γ(xcxa) P0y+tyd=ya+β(ybya)+γ(ycya) P0z+tzd=za+β(zbza)+γ(zcza)

Here we have three equations and three unknowns - t, β, and γ. It’s a linear system we can solve using matrix algebra. First let’s move all the unknowns to one side:

(xaxb)β+(xaxc)γ+xdt=xaP0x (yayb)β+(yayc)γ+ydt=yaP0y (zazb)β+(zazc)γ+zdt=zaP0z

Linear System

Let’s rewrite in matrix form:

[xaxbxaxcxdyaybyaycydzazbzazczd][βγt]=[xaP0xyaP0yzaP0z]

This is a linear system of the form:

Ax=b

This 3x3 linear system can be solved by finding the inverse of the matrix.

Ax=b A1Ax=A1b x=A1b

However, we don’t need to compute the inverse explicitly - there is a faster option.

Cramer’s Rule

Given a linear system of the form

Ax=b

with

x=[x1x2x3] xi=det(Ai)det(A)

where Ai is the matrix formed by replacing the ith column of A by the column vector b.

The denominator det(A) gives us an obvious way to test for intersection - if this is 0, there is no intersection. Cramer’s rule is also convenient because it permits solving for individual elements of the solution vector. So, we can solve for t and check if it’s positive. Them, we can solve for a and check 0α1, etc.

Determinants

Recall, to compute the 2x2 determinant:

det|abcd|=adbc

And for a 3x3 determinant:

det|abcdefghi| =adet|efhi|bdet|dfgi|+cdet|degh| =aeiafhbdi+bfg+cdhceg

Note the pattern of (+) (-) (+) for the three 2x2 determinants. We can compute this “expansion” of the determinant using any row or column, not just the first row (as in this example). However, if you use the middle row or middle column, you flip the signs to (-) (+) (-)

Solve

Below are the results of applying Cramer’s rule. Note the vertical bars around a matrix mean to compute the determinant.

β=|xaP0xxaxcxdyaP0yyaycydzaP0zzazczd||A| γ=|xaxbxaP0xxdyaybyaP0yydzazbzaP0zzd||A| t=|xaxbxaxcxaP0xyaybyaycyaP0yzazbzazczaP0z||A|

Implementation

Common Subterms

3x3 matrices have common subterms that can be exploited by carefully naming variables. Lets look at generic form with dummy variables.

[adgbehcfi][βγt]=[jkl]

Solving for determinants expands to (here we expand the determinant on the third column purposefully):

β=|jdgkehlfi||adgbehcfi| β=j(eihf)+k(gfdi)+l(dheg)a(eihf)+b(gfdi)+c(dheg)

The expression (eihf) is found in two places - instead of computing this twice, you can create a variable for it, compute once, and use it twice. Of course, also save the denominator value - it is the same for all three variables.

Similarly, we can purposefully expand both γ and t computations to share expressions.

γ=|ajgbkhcli|det(A) γ=i(akjb)+h(jcal)+g(blkc)det(A) t=|adjbekcfl|det(A) t=d(blkc)+e(aljc)f(akjb)det(A)

Which we can then manipulate to create common terms with the γ solution.

t=f(akjb)e(jcal)d(blkc)det(A)

Arguably, this also makes cleaner and easier to read/verify code.

Pseudocode

hit_tri(ray r, vert a, vert b, vert c, t0, t1)

    compute t
    if (t < t0) || (t > t1) then
        return false

    compute gamma
    if (gamma < 0) || (gamma > 1) then
        return false

    compute beta
    if (beta < 0) || (beta > 1 - gamma) then
        return false

    return true