31 December 2019

Math - Interpolating a value

Here's how to interpolate a value from one range to another:

Range 1: 100.....550.....1000
Range 2: 300..... x .....3000

This formula will do it:

public static float Interpolate(this float value, float minValue, float maxValue, float minScale, float maxScale) =>
  (((value - minValue) / (maxValue - minValue)) * (maxScale - minScale)) + minScale;

float x = 550.Interpolate(100, 1000, 300, 3000); //x = 1650

Try it

Range 1
Range 2 ...

See also

Scaling a value

16 July 2019

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

I render a 3D triangle. When I click on it, I want to know the point on the triangle that was clicked. To calculate this point, you need:
  • Point position: the window position of the mouse click. You get this point directly from the event raised by the operating system.
  • 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.
  • An InvertMatrix method. You can find an implementation here.
Then you calculate the line that contains all the points that project to the clicked position as follows:
public (Vector3 near, Vector3 far) GetWorldPosition(Point position)
{
    position.Y = viewport.Height - position.Y; //For OpenGL, the bottom is the origin.
    position -= viewport.Position; //Adjust to the viewport's position on the window.
    var p = position.Normalized(Size); //Normalize the point to the range [-1;1].
    var inv = (Projection * Camera).Invert(); //Calculate the inverse matrix.
    var near = new Vector4(p.X, p.Y, -1, 1) * inv;
    var far = new Vector4(p.X, p.Y, 1, 1) * inv;
    near.PerspectiveDivide(); //Divide X, Y and Z by the W component.
    far.PerspectiveDivide();
    return (near, far);
}
If you draw this line, it should appear as a dot until you move the camera, because every vector on this line projects on the same screen pixel.
Intersecting this line with the triangle then gives me the 3D point I'm looking for.

See also

Calculate the 2D window position of a 3D vector

10 April 2019

Math - Generate a UV sphere

The following code generates the vertices and faces of a UV sphere. It is based on this CodeProject article. The code is written in C#, but it's really about the math. It first generates the vertices in a two-dimensional array, and then the faces from that array. The top and bottom are treated separately, because they are only one single vertex. Because of this, the first and final parallel are finished with triangles instead of a quads.
public void GenerateUVSphere(float parallels, float meridians)
{
    ObjectVertex top = null, bottom = null;
    var vertices = new ObjectVertex[(int)Ceiling(parallels) - 1, (int)Ceiling(meridians)];

    for (int parallel = 0; parallel < parallels + 1; parallel++)
    {
        double p = parallel * (PI / parallels);

        for (int meridian = 0; meridian < meridians; meridian++)
        {
            double m = 2 * meridian * (PI / meridians);
            float x = (float)(Sin(p) * Cos(m));
            float y = (float)Cos(p);
            float z = (float)(Sin(p) * Sin(m));

            var vertex = new ObjectVertex(x, y, z);

            if (parallel <= 0)
                top = vertex;
            else if (parallel >= parallels)
                bottom = vertex;
            else
                vertices[parallel - 1, meridian] = vertex;
        }
    }

    for (int parallel = 0; parallel < parallels; parallel++)
    {
        for (int meridian = 0; meridian < meridians; meridian++)
        {
            bool last = meridian >= meridians - 1;

            if (parallel <= 0)
            {
                var a = vertices[parallel, meridian];
                var b = vertices[parallel, last ? 0 : meridian + 1];
                Faces.Add(new ObjectFace(this, top, b, a));
            }
            else if (parallel >= parallels - 1)
            {
                var a = vertices[parallel - 1, meridian];
                var b = vertices[parallel - 1, last ? 0 : meridian + 1];
                Faces.Add(new ObjectFace(this, bottom, a, b));
            }
            else
            {
                var a = vertices[parallel - 1, meridian];
                var b = vertices[parallel - 1, last ? 0 : meridian + 1];
                var c = vertices[parallel, meridian];
                var d = vertices[parallel, last ? 0 : meridian + 1];
                AddQuad(a, b, c, d, false);
            }
        }
    }
}

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