import torch


def look_at(eye, center, world_up):
    """Compute camera viewing matrices.

    Functionality mimes gluLookAt (external/GL/glu/include/GLU/glu.h).

    Args:
        eye: 2D float32 tensor with shape [batch_size, 3] containing the XYZ
            world space position of the camera.
        center: 2D float32 tensor with shape [batch_size, 3] containing a
            position along the center of the camera's gaze line.
        world_up: 2D float32 tensor with shape [batch_size, 3] specifying the
            world's up direction; the output camera will have no tilt with
            respect to this direction.

    Returns:
        A [batch_size, 4, 4] float tensor containing a right-handed camera
        extrinsics matrix that maps points from world space to points in eye
        space.
    """
    batch_size = center.shape[0]
    forward = center - eye

    forward = torch.nn.functional.normalize(forward, dim=1, p=2)

    to_side = torch.cross(forward, world_up)
    to_side = torch.nn.functional.normalize(to_side, dim=1, p=2)

    cam_up = torch.cross(to_side, forward)

    w_column = torch.tensor(batch_size * [[0., 0., 0., 1.]], device=eye.device)
    w_column = torch.reshape(w_column, [batch_size, 4, 1])

    view_rotation = torch.stack([to_side, cam_up, -forward, torch.zeros_like(to_side)], dim=1)  # [batch_size, 4, 3] matrix
    view_rotation = torch.cat([view_rotation, w_column], dim=2)  # [batch_size, 4, 4]

    identity_batch = torch.unsqueeze(torch.eye(3, device=center.device), 0,).repeat([batch_size, 1, 1])
    view_translation = torch.cat([identity_batch, torch.unsqueeze(-eye, 2)], 2)
    view_translation = torch.cat([view_translation, torch.reshape(w_column, [batch_size, 1, 4])], 1)
    camera_matrices = torch.matmul(view_rotation, view_translation)

    return camera_matrices