30 November 2017

Math – 3D line-triangle intersection

The following is a very simple but effective method I found to find the intersection point of a line with a triangle. I used it to determine the height of a point on the terrain.

The first part finds the intersection point:
private float? GetIntersectionY(Vector3 a, Vector3 b, Vector3 c, Vector3 pp) {
  //First, define a ray that will definitely intersect if the triangle lies beneath the point.
  var p = new Vector3(pp.X, 100, pp.Z);
  var q = new Vector3(p.X, p.Y - 100, p.Z);

  //Then, find the intersection.
  var normal = Vector3.CrossProduct(b - a, c - a).Normalized();
  float dist1 = Vector3.DotProduct(p - a, normal);
  float dist2 = Vector3.DotProduct(q - a, normal);

  if (dist1 == dist2 || dist1 * dist2 >= 0) //Parallel; no intersection.
    return null;

  var intersection = p + (q - p) * (-dist1 / (dist2 - dist1));

  //Verify that this point lies inside the triangle.
  if (SameSide(p, a, b, c) && SameSide(p, b, a, c) && SameSide(p, c, a, b))
    return intersection.Y;
  else
    return null;
}

The second part, as the above function already indicates, determines that the point really lies within the triangle, and not just within the mathematical plane. The linked sited explains well how this works.
private static bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b) {
  //It only worked right for me when all the points had the same height though.
  var _p1 = new Vector3(p1.X, 0, p1.Z);
  var _p2 = new Vector3(p2.X, 0, p2.Z);
  var _a = new Vector3(a.X, 0, a.Z);
  var _b = new Vector3(b.X, 0, b.Z);

  //The check.
  var cp1 = Vector3.CrossProduct(_b - _a, _p1 - _a);
  var cp2 = Vector3.CrossProduct(_b - _a, _p2 - _a);
  return Vector3.DotProduct(cp1, cp2) >= 0;
}

21 November 2017

Math – Align a rotation matrix with a normal

Angle and axis

I found this method when I needed to rotate a vehicle to be positioned on the terrain. I had the positions of the wheels, and used those to calculate the vehicle's normal. Then I found the following method to calculate the angle and rotation axis:

var axis = new Vector3(0, 1, 0); //Up vector
float angle = Vector3.DotProduct(normal, axis).Acos(); //I do like extension methods.
var transformation = new Matrix();

if (angle.Absolute() > 0.001f) {
  axis = Vector3.CrossProduct(normal, axis).Normalized();
  transformation.Rotate(angle, axis); //See below.
}

Rotate the matrix

Then the glRotatef function is used to use these values, but that's from the old fixed pipeline OpenGL, and I was using the new OpenGL with shaders and all that. I then found this function that applies the rotation to a matrix:

float rad = angle.ToRadians();
float c = rad.Cos();
float s = rad.Sin();
float t = 1 - c;

float x = axis.X;
float y = axis.Y;
float z = axis.Z;

var rotation = new Matrix();
rotation.M[0, 0] = c + x * x * t;
rotation.M[0, 1] = y * x * t + z * s;
rotation.M[0, 2] = z * x * t - y * s;

rotation.M[1, 0] = x * y * t - z * s;
rotation.M[1, 1] = c + y * y * t;
rotation.M[1, 2] = z * y * t + x * s;

rotation.M[2, 0] = x * z * t + y * s;
rotation.M[2, 1] = y * z * t - x * s;
rotation.M[2, 2] = z * z * t + c;

SetMatrix((this * rotation).M); //This just overwrites the 16 values of this matrix.

See also

Using quaternions to create a rotation matrix

16 October 2017

C# - Using a joypad input device without DirectX

While looking for a way to use my joypad in a C# application, everything I found used DirectInput to do it. As it turns out however, a single simple Windows API call is all that is needed:

internal enum JoypadErrors : uint
{
    None = 0, //NOERROR
    BadDeviceID = 2,
    InvalidParameter = 11,
    BadParameter = 165, //PARMS
    Fault = 166, //NOCANDO
    Unplugged = 167
}

[Flags]
internal enum JoypadFlags : uint //Calibration flags left out.
{
    X = 1,
    Y = 2,
    Z = 4,
    R = 8,
    U = 16,
    V = 32,
    POV = 64,
    Buttons = 128,
    RawData = 256,
    POVCTS = 512,
    Centered = 1024,
    All = X | Y | Z | R | U | V | POV | Buttons
}

[Flags]
internal enum JoypadButtons : uint
{
    I = 1,
    II = 2,
    III = 4,
    IV = 8,
    V = 16,
    VI = 32,
    VII = 64,
    VIII = 128,
    IX = 256,
    X = 512,
    XI = 1024,
    XII = 2048,
    XIII = 4096,
    XIV = 8192,
    XV = 16384,
    XVI = 32768
    //This goes on until button 32.
}

internal struct JoyInfoEx
{
    public int Size;
    public JoypadFlags Flags;
    public int X;
    public int Y;
    public int Z;
    public int R;
    public int U;
    public int V;
    public JoypadButtons Buttons;
    public int ButtonNumber;
    public int POV;
    public int Reserved1;
    public int Reserved2;
}

[DllImport("winmm", EntryPoint = "joyGetPosEx", SetLastError = true)]
private static extern JoypadErrors JoyGetPosEx(uint id, ref JoyInfoEx info);

You ask this function for the current state of the joypad's controls like so:

var info = new JoyInfoEx();
info.Size = Marshal.SizeOf(info); //Don't forget.
info.Flags = JoypadFlags.All; //What you want to get.
var result = JoyGetPosEx(0, ref info); //And that's it.

What do the received values mean? This is how they map to my Trustmaster Firestorm:

The 10 buttons are obvious. All the pressed ones are combined into a single value, so you need to extract them using a bitwise operation.

The point-of-view control returns a value between 0 and 31500, representing an angle in degrees. When unused, it returns 65565.

The left and right stick both have two axes. They all return a value between 0 and 65565. When centered on an axis, it's value is 32767.

And thats all there's to it. You get to work with the values you got.

19 September 2017

Math - rotate a 2D point

I needed a way to rotate a point (x; y) by a certain angle. This turned out to be an easy task:
Vector2 Rotate(Vector2 point, double angle)
{
  double rad = angle * (Math.PI / 180); //First, convert from degrees to radians.
  
  double sin = Math.Sin(rad); //Then, get the sine and
  double cos = Math.Cos(rad); //cosine of this angle.
  
  return new Vector2 //Finally, calculate the rotated point.
  {
    X = point.X * cos - point.Y * sin,
    Y = point.X * sin + point.Y * cos
  };
}

30 August 2017

Math – Calculate the distance between two points in 3D space

Always usefull in 3D development is a method to calculate the distance between two vectors. It's not very hard either, using the Pythagorean theorem. Here is how to do it in C# for two vectors, a and b:
public float Distance(Vector3 a, Vector3 b) =>
  (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2) + Math.Pow(a.Z - b.Z, 2));

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);