import torch


def transform_verts(verts, T):
    """
    Transform vertices using a 4x4 transformation matrix

    Inputs:
    - verts: FloatTensor of shape (N, V, 3) giving a batch of vertex positions.
    - T: FloatTensor of shape (N, 4, 4) giving transformation matrices

    Outputs:
    - verts_out: FloatTensor of shape (N, V, 4)
                 giving vertex homogeneous positions (x, y, z, w)
        where verts_out[i] is the result of transforming verts[i] by T[i].
    """
    N, V = verts.shape[0], verts.shape[1]
    dtype, device = verts.dtype, verts.device

    # Add an extra row of ones to the world-space coordinates of verts before
    # multiplying by the projection matrix. We could avoid this allocation by
    # instead multiplying by a 4x3 submatrix of the projectio matrix, then
    # adding the remaining 4x1 vector. Not sure whether there will be much
    # performance difference between the two.
    ones = torch.ones(N, V, 1, dtype=dtype, device=device)
    verts_hom = torch.cat([verts, ones], dim=2)
    verts_cam_hom = torch.bmm(verts_hom, T.transpose(1, 2))
    return verts_cam_hom