27 January 2019

Math - Calculate the 2D window position of a 3D vector

I render a 3D model as points in a viewport with OpenGL. I want to select points by clicking them. To do this, I calculate the position of the 3D point on the 2D window the same way the shader does. These variables are needed:

  • Vector4 vector: the 3D position in object space of the point + it's W component. W is always 1 in object space, but gets transformed by the projection matrix. It is then used for the perspective divide.
  • Matrix projection and camera (also called modelview): the matrices used to transform from object space to world space and then to 2D clip coordinates.
  • Rectangle viewport: the position and size of the viewport on which to project.
  • Point mouse: the window position of the mouse click. You get this point directly from the event raised by the operating system.

From then, it's actually not that hard. You do need a method to multiply matrices.

public Point GetPosition(Vector4 vector)
{
    var p = Projection * Camera * vector;

    //Perspective divide.
    p.X /= p.W;
    p.Y /= p.W;

    //Scale from [-1; 1] to viewport dimensions.
    p.X = (p.X + 1).Scale(2, viewport.Width);
    p.Y = -(p.Y - 1).Scale(2, viewport.Height); //For OpenGL, the bottom is the origin.

    //Adjust to the viewport's position on the window.
    return new Point((int)p.X + viewport.Left, (int)p.Y + viewport.Top);
}

var pos = GetPosition(vector);
IsSelected = pos.X.Between(mouse.X - 5, mouse.X + 5) && pos.Y.Between(mouse.Y - 5, mouse.Y + 5);

See also

Calculate the 3D world position of a 2D window vector