Linear Triangle Elements

In previous discussions, we learned to calculate and its derivatives with respect to . For simulation, however, we require and . This necessitates a clear understanding of , as it allows us to employ the chain rule to derive these derivatives with respect to effectively.

In 2D simulations, we often divide the solid domain into non-degenerate triangular elements. Assume the mapping is linear within each triangle, thus keeping the deformation gradient constant. Referencing Example 12.2.1, for a triangle defined by vertices , we have the equations: where denotes the world-space coordinates of the triangle vertices. This relationship leads to the expression for : Equation (15.1.1) shows that , derived here, maps any segment within the triangle to its world-space counterpart through linear combinations of the triangle edges and . A more general and rigorous derivation of this formula will be presented in subsequent chapters.

Once is established, we can calculate its derivative with respect to for each triangle as follows: where represents the inverse of the matrix formed by subtracting the first vertex from the second and third vertices. This matrix can be precomputed at initialization along with other properties such as the volume and Lame parameters of each triangle:

Implementation 15.1.1 (Precomputation of element information, simulator.py).

# rest shape basis, volume, and lame parameters
vol = [0.0] * len(e)
IB = [np.array([[0.0, 0.0]] * 2)] * len(e)
for i in range(0, len(e)):
    TB = [x[e[i][1]] - x[e[i][0]], x[e[i][2]] - x[e[i][0]]]
    vol[i] = np.linalg.det(np.transpose(TB)) / 2
    IB[i] = np.linalg.inv(np.transpose(TB))
mu_lame = [0.5 * E / (1 + nu)] * len(e)
lam = [E * nu / ((1 + nu) * (1 - 2 * nu))] * len(e)

The Young's modulus and Poisson's ratio:

E = 1e5         # Young's modulus
nu = 0.4        # Poisson's ratio

Here, e no longer stores all edge elements as in mass-spring models but represents all triangle elements, which can be generated by modifying the meshing code as follows:

Implementation 15.1.2 (Assembling per-triangle vertex indices, square_mesh.py).

    # connect the nodes with triangle elements
    e = []
    for i in range(0, n_seg):
        for j in range(0, n_seg):
            # triangulate each cell following a symmetric pattern:
            if (i % 2)^(j % 2):
                e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j, i * (n_seg + 1) + j + 1])
                e.append([(i + 1) * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1, i * (n_seg + 1) + j + 1])
            else:
                e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1])
                e.append([i * (n_seg + 1) + j, (i + 1) * (n_seg + 1) + j + 1, i * (n_seg + 1) + j + 1])

Triangles are arranged in a symmetric pattern and can be rendered by drawing the three edges:

Implementation 15.1.3 (Draw triangles, simulator.py).

        pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[0]]), screen_projection(x[eI[1]]))
        pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[1]]), screen_projection(x[eI[2]]))
        pygame.draw.aaline(screen, (0, 0, 255), screen_projection(x[eI[2]]), screen_projection(x[eI[0]]))