09 April 2017

Math – Calculate a transformation matrix

Suppose you have a translation, a rotation and a scaling matrix; how do you combine them into one transformation matrix — also model matrix — used to render some object? The order in which they are combined is also important. The following method will first rotate and scale the object, then move it. Rotation is the most complex transformation, and is done with quaternions.

//"Position", "Scale" and "Rotation" are all Vector3's.
Quaternion rot = null;
if (Rotation != null)
{
    var rotX = new Quaternion(Rotation.X, 1, 0, 0);
    var rotY = new Quaternion(Rotation.Y, 0, 1, 0);
    var rotZ = new Quaternion(Rotation.Z, 0, 0, 1);
    rot = rotX * rotY * rotZ;
}

var model = new Matrix();
model.Rotate(rot);
model.Scale(Scale);
model.Translate(Position);

The Matrix class was also mentioned in the post about quaternions. You can find the Matrix.Rotate method there as well. Compared to that, translation and scaling are pretty simple:

//An overload of this method takes a Vector3, and checks it for NULL.
public void Translate(float x, float y, float z)
{
    M[0, 3] += x;
    M[1, 3] += y;
    M[2, 3] += z;
}

//Also has an overload that takes a Vector3.
public void Scale(float x, float y, float z)
{
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            M[i, j] *= x;
            M[i, j] *= y;
            M[i, j] *= z;
        }
    }
}

11 March 2017

Windows API - Retrieve the size of the screen

Retrieving the size — in pixels that is — of the desktop screen is not that hard. Here's how to do it:
//Retrieve the window size.
IntPtr windowHandle = GetDesktopWindow();
Rect windowSize;
GetWindowRect(windowHandle, out windowSize);

//DLL import methods.
[DllImport("user32", SetLastError = true)]
public static extern IntPtr GetDesktopWindow();

[DllImport("user32", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr windowHandle, out Rect rectangle);

//Rect(angle) struct.
public struct Rect
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

19 February 2017

Math - Calculate a look-at matrix for OpenGL

The final piece of the puzzle to get my basic OpenGL scene up and running was the camera or look-at matrix. It is calculated from a position vector and a direction vector; the look-at vector.

The position and direction vectors

Here's how the position and direction vectors are calculated. The input comes from the mouse and keyboard. Note that I do use some extension methods to make life easier. Delta time is a time-based value to get the same speed on slow and fast computers.

rotationHorizontal += window.MouseMovement.X * 0.1f;
rotationHorizontal = rotationHorizontal.Circular(0, 359);
rotationVertical += window.MouseMovement.Y * 0.1f;
rotationVertical = rotationVertical.Constrain(-89, 89);

float h = rotationHorizontal.ToRadians();
float v = rotationVertical.ToRadians();
float hs = h.Sin();
float hc = h.Cos();
float vs = v.Sin();
float vc = v.Cos();
float speed = 0.005f * deltaTime;

if (window.KeyDown(Key.Up))
{
    position.X -= hc * speed;
    position.Z -= hs * speed;
}

if (window.KeyDown(Key.Down))
{
    position.X += hc * speed;
    position.Z += hs * speed;
}

if (window.KeyDown(Key.Left))
{
    position.X -= hs * speed;
    position.Z += hc * speed;
}

if (window.KeyDown(Key.Right))
{
    position.X += hs * speed;
    position.Z -= hc * speed;
}

var direction = new Vector(vc * hc, vs, vc * hs);
var camera = new Matrix(position, direction);

The look-at matrix

This one is again implemented as a constructor of the Matrix class. It uses some basic vector operations; the implementations of which can easily be found online.

public Matrix(Vector position, Vector direction)
{
    var up = new Vector(0, 1, 0);
    var zAxis = direction.Normalized();
    var xAxis = Vector.CrossProduct(up.Normalized(), zAxis).Normalized();
    var yAxis = Vector.CrossProduct(zAxis, xAxis);

    var translation = new Matrix();
    translation.Translate(position.Negated());

    var rotation = new Matrix();
    rotation.M[0, 0] = xAxis.X;
    rotation.M[0, 1] = xAxis.Y;
    rotation.M[0, 2] = xAxis.Z;

    rotation.M[1, 0] = yAxis.X;
    rotation.M[1, 1] = yAxis.Y;
    rotation.M[1, 2] = yAxis.Z;

    rotation.M[2, 0] = zAxis.X;
    rotation.M[2, 1] = zAxis.Y;
    rotation.M[2, 2] = zAxis.Z;

    var lookAt = rotation * translation;
    SetMatrix(lookAt.M);
}

See also

Simplified method to create a look-at matrix
Calculate a perspective projection matrix for OpenGL

09 February 2017

Math - Calculate a perspective projection matrix for OpenGL

Another method that took a while to find was the one that calculates a perspective projection matrix from a given field of view, aspect ratio and near and far plane to work with OpenGL. This constructor for a Matrix object does it though:

public Matrix(float fov, float aspect, float near, float far)
{
    Values = new float[4, 4];

    float scale = (float)Math.Tan(fov.ToRadians() * 0.5f) * near;
    float right = aspect * scale;
    float left = -right;
    float top = scale;
    float bottom = -top;

    Values[0, 0] = 2f * near / (right - left);
    Values[1, 1] = 2f * near / (top - bottom);
    Values[0, 2] = (right + left) / (right - left);
    Values[1, 2] = (top + bottom) / (top - bottom);
    Values[2, 2] = -(far + near) / (far - near);
    Values[3, 2] = -1f;
    Values[2, 3] = -2f * far * near / (far - near);
}

Note that this class uses row-major order, while OpenGL uses column-major order. When I pass my matrices to the glUniformMatrix4fv method, I set it's transpose parameter to True.

See also

Calculate a look-at matrix for OpenGL

04 February 2017

Math – Using quaternions to create a rotation matrix

The best way I found to create a rotation matrix with all three axes is with quaternions. The following code is C#:

Initialize

The Quaternion class has four properties: W, X, Y and Z. "W" is the angle, and the other represent the axis. I will only use unit quaternions, meaning the rotation is around one of the three axes only. You initialize an identity quaternion by setting them to (1, 0, 0, 0). I create one for each rotation like so:

var rotx = new Quaternion(Rotation.X, 1, 0, 0);
var roty = new Quaternion(Rotation.Y, 0, 1, 0);
var rotz = new Quaternion(Rotation.Z, 0, 0, 1);

The quaternion's constructor initializes those arguments as:

double r = (angle * (Math.PI / 180));
W = (float)Math.Cos(r / 2);

double s = Math.Sin(r / 2);
X = (float)(x * s);
Y = (float)(y * s);
Z = (float)(z * s);

Multiply

Next, we combine them all into one quaternion:

var rot = rotx * roty * rotz;

The method that multiplies two quaternions is defined as follows:

public static Quaternion operator *(Quaternion a, Quaternion b)
{
    var c = new Quaternion();
    c.W = a.W * b.W - a.X * b.X - a.Y * b.Y - a.Z * b.Z;
    c.X = a.W * b.X + a.X * b.W + a.Y * b.Z - a.Z * b.Y;
    c.Y = a.W * b.Y - a.X * b.Z + a.Y * b.W + a.Z * b.X;
    c.Z = a.W * b.Z + a.X * b.Y - a.Y * b.X + a.Z * b.W;
    return c;
}

Rotate

To get the matrix I needed to pass to OpenGL, I created another class called "Matrix", which is a 4x4 matrix containing a 16 element array. Then a method takes the quaternion and adds the rotation to this matrix. It took a while to find a way to do this that worked with OpenGL, but I finally found it in the .net framework.

var matrix = new Matrix();
matrix.Rotate(rot); //"rot" being the quaternion from before.

public void Rotate(Quaternion rotation)
{
    float x2 = rotation.X + rotation.X;
    float y2 = rotation.Y + rotation.Y;
    float z2 = rotation.Z + rotation.Z;

    float xx = rotation.X * x2;
    float xy = rotation.X * y2;
    float xz = rotation.X * z2;
    float yy = rotation.Y * y2;
    float yz = rotation.Y * z2;
    float zz = rotation.Z * z2;
    float wx = rotation.W * x2;
    float wy = rotation.W * y2;
    float wz = rotation.W * z2;

    Values[0, 0] = 1.0f - (yy + zz);
    Values[0, 1] = xy + wz;
    Values[0, 2] = xz - wy;

    Values[1, 0] = xy - wz;
    Values[1, 1] = 1.0f - (xx + zz);
    Values[1, 2] = yz + wx;

    Values[2, 0] = xz + wy;
    Values[2, 1] = yz - wx;
    Values[2, 2] = 1.0f - (xx + yy);
}

See also

Align a rotation matrix with a normal

14 January 2017

OpenGL – Calling version 1.1+ functions from .net under Windows

I learned that the Windows OpenGL DLL only exports OpenGL 1.1 functions. To use newer functions, you must obtain the function pointer first. Conveniently, the following method is provided to do so:

[DllImport("opengl32", EntryPoint = "wglGetProcAddress", SetLastError = true)]
private static extern IntPtr GetProcAddressExtern(string name);
internal static IntPtr GetProcAddress(string name)
{
    var address = GetProcAddressExtern(name);
    if (address == IntPtr.Zero) throw new ApplicationException($@"Get procedure address for ""{name}""");
    return address;
}

To call the method, you must define a delegate for it, and use it as follows:

private delegate void GenBuffersDelegate(int n, uint[] buffers);
internal static void GenBuffers(int n, uint[] buffers)
{
    var funcptr = GetProcAddress("glGenBuffers");
    var func = Marshal.GetDelegateForFunctionPointer(funcptr);
    func(n, buffers);
}

There is a performance cost however. Therefore it's best to store the function once retrieved. I also used a generic method to make the whole thing more convienient.

private static Dictionary<string, object> Functions = new Dictionary<string, object>();
internal static Del Function<del>() where Del : class
{
    var type = typeof(Del).Name;
    if (Functions.ContainsKey(type))
        return Functions[type] as Del;

    IntPtr ptr = GetProcAddress(type);
    if (ptr == null || ptr == IntPtr.Zero)
        throw new ApplicationException($@"Pointer to OpenGL function ""{type}"" could not be retrieved.");
    var function = Marshal.GetDelegateForFunctionPointer<del>(ptr);
    Functions.Add(type, function);
    return function;
}

The same method can now simply be called like this:

private delegate void glGenBuffers(int n, uint[] ids);
public static void GenBuffers(int n, uint[] ids) => Win.GL.Function<glGenBuffers>()(n, ids);

08 January 2017

.net – Running an OpenGL loop written in C# using P/Invoke for Windows

As the title already explains, what follows will be all the parts needed to initiate OpenGL and run a (game) drawing loop. Everything is written in C#, communicating with Windows through P/Invoke, and no additional references are included.

Windows core functions


These are the Windows functions that will be P/Invoked to create the window to draw on. You could simply create a .net form, but somehow .net gets in the way of OpenGL, and it doesn't work properly. The following is all part of a static class called Windows.Core:
internal const int WINDOW_CLASS_STYLE_OWN_DEVICE_CONTEXT = 0x0020;
internal const uint WINDOW_STYLE_VISIBLE = 0x10000000;
internal const uint WINDOW_STYLE_CAPTION = 0x00C00000;
internal const uint WINDOW_STYLE_POPUP_WINDOW = 0x80000000 | 0x00800000 | 0x00080000;

public delegate int WindowProcedure(IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam);

[StructLayout(LayoutKind.Sequential)]
private struct WindowClassEx
{
    public uint Size;
    public int Styles;
    [MarshalAs(UnmanagedType.FunctionPtr)]
    public WindowProcedure WindowProcedure;
    public int ClassExtra;
    public int WindowExtra;
    public IntPtr Instance;
    public IntPtr Icon;
    public IntPtr Cursor;
    public IntPtr Background;
    public string MenuName;
    public string ClassName;
    public IntPtr IconSmall;
}

[DllImport("user32", SetLastError = true)]
private static extern IntPtr GetDC(IntPtr hWnd);
internal static IntPtr GetDeviceContext(IntPtr windowHandle)
{
    IntPtr result = GetDC(windowHandle);
    if (result == IntPtr.Zero) throw new ApplicationException("Get device context");
    return result;
}

[DllImport("user32", SetLastError = true)]
private static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
internal static void ReleaseDeviceContext(IntPtr windowHandle, IntPtr deviceContext)
{
    if (deviceContext != IntPtr.Zero)
        if (!ReleaseDC(windowHandle, deviceContext))
            throw new ApplicationException("Release device context");
}

[DllImport("kernel32", EntryPoint = "RtlZeroMemory", SetLastError = true)]
internal static extern void ZeroMemory(IntPtr dest, IntPtr size);

[DllImport("user32", EntryPoint = "SetForegroundWindow", SetLastError = true)]
private static extern bool SetForegroundWindowExtern(IntPtr hWnd);
internal static void SetForegroundWindow(IntPtr deviceContext)
{
    if (!SetForegroundWindowExtern(deviceContext))
        Debug.WriteLine("Could not set foreground window.");
}

[DllImport("kernel32", SetLastError = true)]
internal static extern int GetLastError();

[DllImport("user32", SetLastError = true)]
private static extern short RegisterClassEx(ref WindowClassEx windowClass);
internal static short RegisterClass(IntPtr instanceHandle, string className, WindowProcedure windowProcedure)
{
    var windowClass = new Windows.Core.WindowClassEx
    {
        Styles = Windows.Core.WINDOW_CLASS_STYLE_OWN_DEVICE_CONTEXT,
        WindowProcedure = windowProcedure,
        Instance = instanceHandle,
        ClassName = className
    };
    windowClass.Size = (uint)Marshal.SizeOf(windowClass);

    short result = RegisterClassEx(ref windowClass);
    if (result == (short)0) throw new ApplicationException("Register class");
    return result;
}

[DllImport("user32", SetLastError = true)]
private static extern IntPtr CreateWindowEx(uint extendedStyles, string className, string windowName, uint styles, int x, int y,
    int width, int height, IntPtr parent, IntPtr menu, IntPtr instance, IntPtr param);
internal static IntPtr CreateWindow(IntPtr instanceHandle, string className, string title)
{
    IntPtr result = CreateWindowEx(0, className, title,
      Windows.Core.WINDOW_STYLE_CAPTION | Windows.Core.WINDOW_STYLE_POPUP_WINDOW | Windows.Core.WINDOW_STYLE_VISIBLE,
      0, 0, 640, 480, IntPtr.Zero, IntPtr.Zero, instanceHandle, IntPtr.Zero);
    if (result == IntPtr.Zero) throw new ApplicationException("Create window");
    return result;
}

[DllImport("user32", EntryPoint = "DestroyWindow", SetLastError = true)]
private static extern bool DestroyWindowExtern(IntPtr hWnd);
internal static void DestroyWindow(IntPtr deviceContext)
{
    if (deviceContext != IntPtr.Zero)
        if (!DestroyWindowExtern(deviceContext))
            throw new ApplicationException("Destroy window");
}

Windows GDI functions


Some more functions from Windows.GDI:

internal const int PIXEL_FORMAT_DOUBLE_BUFFER = 0x00000001;
internal const int PIXEL_FORMAT_DRAW_TO_WINDOW = 0x00000004;
internal const int PIXEL_FORMAT_SUPPORT_OPENGL = 0x00000020;
internal const int PIXEL_FORMAT_TYPE_RGBA = 0;
internal const int PIXEL_FORMAT_LAYER_MAIN_PLANE = 0;

internal struct PixelFormatDescriptor
{
    public short Size;
    public short Version;
    public int Flags;
    public byte PixelType;
    public byte ColorBits;
    public byte RedBits;
    public byte RedShift;
    public byte GreenBits;
    public byte GreenShift;
    public byte BlueBits;
    public byte BlueShift;
    public byte AlphaBits;
    public byte AlphaShift;
    public byte AccumBits;
    public byte AccumRedBits;
    public byte AccumGreenBits;
    public byte AccumBlueBits;
    public byte AccumAlphaBits;
    public byte DepthBits;
    public byte StencilBits;
    public byte AuxBuffers;
    public byte LayerType;
    public byte Reserved;
    public int LayerMask;
    public int VisibleMask;
    public int DamageMask;
}

internal static PixelFormatDescriptor GetPixelFormatDescriptor(byte colorBits, byte depthBits)
{
    var pixelFormat = new PixelFormatDescriptor();
    unsafe
    {
        Windows.Core.ZeroMemory((IntPtr)(&pixelFormat), (IntPtr)Marshal.SizeOf(pixelFormat));
    }
    pixelFormat.Size = (short)Marshal.SizeOf(pixelFormat);
    pixelFormat.Version = 1;
    pixelFormat.Flags = PIXEL_FORMAT_DOUBLE_BUFFER | PIXEL_FORMAT_DRAW_TO_WINDOW | PIXEL_FORMAT_SUPPORT_OPENGL;
    pixelFormat.PixelType = PIXEL_FORMAT_TYPE_RGBA;
    pixelFormat.ColorBits = colorBits;
    pixelFormat.LayerType = PIXEL_FORMAT_LAYER_MAIN_PLANE;
    pixelFormat.DepthBits = depthBits;
    return pixelFormat;
}

[DllImport("gdi32", EntryPoint = "ChoosePixelFormat", SetLastError = true)]
private static extern int ChoosePixelFormatExtern(IntPtr hDC, ref PixelFormatDescriptor descriptor);
internal static int ChoosePixelFormat(IntPtr deviceContext, ref PixelFormatDescriptor descriptor)
{
    int result = ChoosePixelFormatExtern(deviceContext, ref descriptor);
    if (result == 0) throw new ApplicationException("Choose pixel format");
    return result;
}

[DllImport("gdi32", EntryPoint = "SetPixelFormat", SetLastError = true)]
private static extern bool SetPixelFormatExtern(IntPtr hDC, int format, ref PixelFormatDescriptor descriptor);
internal static void SetPixelFormat(IntPtr deviceContext, int format, ref PixelFormatDescriptor descriptor)
{
    if (!SetPixelFormatExtern(deviceContext, format, ref descriptor))
        throw new ApplicationException("Set pixel format");
}

[DllImport("gdi32", EntryPoint = "SwapBuffers", SetLastError = true)]
private static extern bool SwapBuffersExtern(IntPtr hDC);
internal static void SwapBuffers(IntPtr deviceContext)
{
    if (!SwapBuffersExtern(deviceContext))
        throw new ApplicationException("Swap buffers");
}

Windows OpenGL functions


And some Windows-specific OpenGL functions:

[DllImport("opengl32", EntryPoint = "wglCreateContext", SetLastError = true)]
private static extern IntPtr CreateContextExtern(IntPtr hDC);
internal static IntPtr CreateContext(IntPtr deviceContext)
{
    IntPtr result = CreateContextExtern(deviceContext);
    if (result == IntPtr.Zero) throw new ApplicationException("Create render context");
    return result;
}

[DllImport("opengl32", EntryPoint = "wglMakeCurrent", SetLastError = true)]
private static extern bool MakeCurrentExtern(IntPtr hDC, IntPtr renderContext);
internal static void MakeCurrent(IntPtr deviceContext, IntPtr renderContext)
{
    if (!MakeCurrentExtern(deviceContext, renderContext))
        throw new ApplicationException(renderContext == IntPtr.Zero ? "Make NULL current" : "Make render context current");
}

[DllImport("opengl32", EntryPoint = "wglDeleteContext", SetLastError = true)]
private static extern bool DeleteContextExtern(IntPtr renderContext);
internal static void DeleteContext(IntPtr renderContext)
{
    if (renderContext != IntPtr.Zero)
        if (!DeleteContextExtern(renderContext))
            throw new ApplicationException("Delete render context");
}

OpenGL functions


And finally some actual OpenGL functions, so there's something to see:

internal const int COLOR_BUFFER_BIT = 16384;

[DllImport("opengl32", EntryPoint = "glClear", SetLastError = true)]
internal static extern void Clear(short mask);

[DllImport("opengl32", EntryPoint = "glClearColor", SetLastError = true)]
internal static extern void ClearColor(float red, float green, float blue, float alpha);

[DllImport("opengl32", EntryPoint = "glFinish", SetLastError = true)]
internal static extern void Finish();

[DllImport("opengl32", EntryPoint = "glFlush", SetLastError = true)]
internal static extern void Flush();

Main program


With all of that in place, a fairly simple program can be put together. It's of type "Windows Application", but I removed the auto-created form, as well as the reference to Windows.Forms. It also contains the method that allows Windows to communicate with the program while it's running. This one doesn't really do anything useful for us, but returning that "1" is necessary to get the window created. The following code is in the static Main method:

var windowProcedure = new Windows.Core.WindowProcedure((IntPtr hWnd, uint message, IntPtr wParam, IntPtr lParam) =>
{
    return message == 1 || message == 129 || message == 32 ? 1 : 0;
});

IntPtr instanceHandle = Process.GetCurrentProcess().Handle;
string className = "EGSDemolisher3";
Windows.Core.RegisterClass(instanceHandle, className, windowProcedure);
IntPtr windowHandle = Windows.Core.CreateWindow(instanceHandle, className, "Demolisher III");
IntPtr deviceContext = Windows.Core.GetDeviceContext(windowHandle);
Windows.GDI.PixelFormatDescriptor descriptor = Windows.GDI.GetPixelFormatDescriptor(24, 16);
var format = Windows.GDI.ChoosePixelFormat(deviceContext, ref descriptor);
Windows.GDI.SetPixelFormat(deviceContext, format, ref descriptor);
IntPtr renderContext = Windows.OpenGL.CreateContext(deviceContext);
Windows.OpenGL.MakeCurrent(deviceContext, renderContext);
Windows.Core.SetForegroundWindow(deviceContext);

var endTime = DateTime.Now.AddSeconds(5);
int second = DateTime.Now.Second;
int frames = 0;
while (DateTime.Now < endTime)
{
    if (DateTime.Now.Second != second)
    {
        Debug.WriteLine($"FPS {second}': {frames}");
        second = DateTime.Now.Second;
        frames = 0;
    }
    frames++;

    if (second % 2 == 0)
        OpenGL.ClearColor(.5f, .5f, 1, 1);
    else
        OpenGL.ClearColor(1, .5f, 0, 1);

    OpenGL.Clear(OpenGL.COLOR_BUFFER_BIT);
    OpenGL.Flush();
    OpenGL.Finish();

    Windows.GDI.SwapBuffers(deviceContext);
}

Windows.OpenGL.MakeCurrent(IntPtr.Zero, IntPtr.Zero);
Windows.OpenGL.DeleteContext(renderContext);
Windows.Core.ReleaseDeviceContext(windowHandle, deviceContext);
Windows.Core.DestroyWindow(windowHandle);