Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

An Analog Clock Design Using OpenTK in C#

4.59/5 (22 votes)
17 Dec 2013CPOL5 min read 81K   4.5K  
A 2D analog clock designed using OpenTK in C# and WinForms.

Introduction

The Open Toolkit is an advanced, low-level C# library that wraps OpenGL, OpenCL, and OpenAL. It is suitable for games, scientific applications, and any other project that requires 3D graphics, audio, or computing functionality. In short, it's called OpenTK.

Background

It is not a fast C# implementation of OpenGL, but it is the best. There are some previous ones like CSGL, TAO framework, etc., but they are incomplete and bound by some limitations. And now, they are completely unable to keep with .NET Framework.

Every OpenGL function, we can call by a GL class like glVertex2f(-2.0,-2.0) of OpenGL used in OpenTk as GL.Vertex2(-2.0,-2.0). It has a very nice and flexible GUI option, cross-platform GLControl (Windows.Forms) which can easily be added in the Visual Studio toolbox, GLWidget which is another rich and useful component for (GTK#) and WPFControl classes. And there is also a native, high-performance GameWindow which is designed specifically for games. You can develop a game faster than you think.

It also has a very useful API collection like 3D Math Toolkit that supplies Vector, Matrix, Quaternion, and Bezier structs. The Input API provides keyboard, mouse, and joystick interfaces. The Display API helps for multiple monitors. OpenTK.Compatibility supports TAO framework applications.

And if you want to think about platform independency, it supports 32- and 64-bit versions of Windows, Linux, and Mac OS X. No need for mismanaged libraries – compile once, run everywhere. And the great thing is that you are free to use, modify and redistribute the source code. It is suitable for open- and closed-source projects alike.

Using the code

You don’t need to have any prior knowledge in OpenGL or C# graphics to learn OpenTK. Only some basics of Windows Form design is more than enough.

For using OpenTK, you need to add two DLLs in your Visual Studio reference. They are OpenTK.dll and OpenTK.Graphics.OpenGL.dll. Both can be found easily here.

We are not going to use the well known game window for OpenTK, as I guess you are familiar with Windows Form design. We are just using a normal Windows Form. OpenTK provides a very nice control/tool. For that, you need to add it in your Visual Studio toolbox, it is called Glcontrol and you can find it here. For that, first click the toolbox and choose the item, then browse and then add the DLL for this control. Now you can use it.

To begin with, create a Form on which you will place your GLControl. Right click in some empty space of the toolbox, pick “Choose Items…”, and browse for OpenTK.GLControl.dll. Make sure you can find the GLControl listed in the .NET Framework Components, as in the image below.

Click to enlarge image

Then you can add the GLControl to your form as any .NET control. A GLControl named glControl1 will be added to your Form.

So, first add this control and named glControl1 in your port and write the below code. Also add this method in this control load event. For further details, you can go here.

C#
private void glControl1_Resize(object sender, EventArgs e)
{
   int w = glControl1.Width;
   int h = glControl1.Height;
   glControl1.MakeCurrent();
   GL.MatrixMode(MatrixMode.Projection);
   GL.LoadIdentity();
   GL.ClearColor(Color.SkyBlue);
   GL.Ortho(-w / 2, w / 2, -h / 2, h / 2, -1, 1);
   GL.Viewport(0, 0, w, h);
   GL.End();
   glControl1.SwapBuffers();
}

If we run it, we will find a from like below:

undefined

So how is it done? It's easy. First, we see:

C#
glControl1.MakeCurrent();

It makes all the next GL commands enabled for this control.

C#
GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();

We make that matrix mode in projection and then load the identity. There are four types of modes for 2D projection, if you want to know more about these modes, you can see them here.

The next command is very much important to understand. For that, first we need to be good OpenGL citizens and setup an orthographic projection matrix using GL.Ortho(). We need to call GL.Viewport() also.

GL.Ortho(-w / 2, w / 2, -h / 2, h / 2, -1, 1);

This makes the center of the GL box 0,0 for axis x,y. Because w is the width and h is the height of the GL box. If you want to make bottom-left corner pixel as 0,0, you can write:

C#
GL.Ortho(0, w, 0, h, -1, 1)
GL.Viewport(0, 0, w, h);

Viewport is used to select the painting area in the control.

C#
GL.ClearColor(Color.SkyBlue);

and it makes it blue. For command disposal and window buffer, we have to write:

C#
GL.End();
glControl1.SwapBuffers();

Now, at first, we have to draw a circle in our glcontrol. For the draw method, we have to use the paint method in the paint event in glcontrol, like below:

C#
private void glControl1_Paint(object sender, PaintEventArgs e)
{
  GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit)
  drawclock();
  glControl1.SwapBuffers();
}

void Draw_clock()
{
   drawCircle(80);//80 is radius of the circle
   Draw_digit();
}

For drawcircle, I use the following code:

C#
void drawCircle(float radius)
{
    GL.Color3(Color.White);
    GL.Begin(BeginMode.TriangleFan);

    for (int i = 0; i < 360; i++)
    {
        double degInRad = i * 3.1416/180;
        GL.Vertex2(Math.Cos(degInRad) * radius, Math.Sin(degInRad) * radius);
    }
    GL.End;
}

Image 3

Now, it looks like it happens so easily.

C#
GL.Begin(BeginMode.TriangleFan);

This mode draws a triangle and fills it with white color using the provided vertexes point. All these triangles make the circle, like triangle pizza pieces make a circlular pizza :).

If you want to know about GL.Begin, see here. You must remember to write GL.End(); after GL.Begin(); otherwise nothing will happen and the compiler can't give you an error for that. Now we have to draw a digit and two lines for the minute and hour.

Write simple code for a normal vertex operation like that. Remember that the radius of the circle is 80.

C#
void Draw_digit()
{
    GL.MatrixMode(MatrixMode.Projection);
    GL.LoadIdentity();
    GL.Color3(Color.Red);
   //for hour
    GL.Begin(BeginMode.TriangleFan);

    GL.Vertex2(0, +5);
    GL.Vertex2(0, -5);
    GL.Vertex2(70, 0);
    GL.Vertex2(70, 0);
    GL.Color3(Color.Red);

    GL.End();

    //for minute
    GL.Begin(BeginMode.TriangleFan);
    GL.Vertex2(+5, 0);
    GL.Vertex2(-5, 0);
    GL.Vertex2(-65, 40);
    GL.Vertex2(-65, 40);
    GL.End();
    GL.Color3(Color.Black);

    //for draw digit III
    GL.Begin(BeginMode.Lines);
    GL.Vertex2(5, 60);
    GL.Vertex2(5, 70);
    GL.Vertex2(0, 60);
    GL.Vertex2(0, 70);

    GL.Vertex2(-5, 70);
    GL.Vertex2(-15, 60);
    GL.Vertex2(-15, 70);
    GL.Vertex2(-5, 60);

    GL.End();

    GL.Color3(Color.Black);
    //for draw digit XII
    GL.Begin(BeginMode.Lines);

    GL.Vertex2(60,0);
    GL.Vertex2(60,8);
    GL.Vertex2(70,0);
    GL.Vertex2(70,8);
    GL.Vertex2(65, 0);
    GL.Vertex2(65, 8);

    GL.End();
    GL.Color3(Color.Black);
    //for draw digit IV
    GL.Begin(BeginMode.Lines);

    GL.Vertex2(10, -60);
    GL.Vertex2(10, -70);
    GL.Vertex2(0, -60);
    GL.Vertex2(0, -70);
    GL.Vertex2(5, -60);
    GL.Vertex2(0, -70);
    GL.Vertex2(5, -60);
    GL.Vertex2(0, -70);

    GL.End();
    GL.Color3(Color.Black);
    //for draw digit IX
    GL.Begin(BeginMode.Lines);
    GL.Vertex2(-75,-5);
    GL.Vertex2(-75,-15);

    GL.Vertex2( -70,-5);
    GL.Vertex2(-60,-15);
    GL.Vertex2(- 70,-15);
    GL.Vertex2(-60,-5);
}

Here is code for connecting one vertex to another vertex. For details, you can look at similar OpenGL functions here.

After drawing that, we see our output form is as shown below:

Image 5

Now we have to create a timer event for that, so first add a timer select interval at 1000 for 1s, enable it, and add this event at timer tick:

C#
private void timer1_Tick(object sender, EventArgs e)
{
   glControl2.MakeCurrent();
   PaintEventArgs p = null;
   glControl2_Paint(sender,p);
   GL.End();
}

Now the paint method will be called after one second. You have to draw a line for the second there, so the paint event will be like:

C#
private void glControl2_Paint(object sender, PaintEventArgs e)
{
    glControl2.MakeCurrent();

    GL.End();
    glControl2.SwapBuffers();
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    Draw_clock();
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
    GL.End();
    glControl2.SwapBuffers();
    drawsecond();
    glControl2.SwapBuffers();
}

Here for drawing seconds, we need a static variable. For changing the coordinate of the seconds line vertex position, we take:

C#
static int i = 0;

Now the drawsecond() function:

C#
void  drawsecond()
{
    GL.Color3(Color.Red);
    GL.Begin(BeginMode.Quads);

    GL.Vertex2(5, 0);
    GL.Vertex2(-5, 5);
    double degInRad = i * 3.1416 / 180;
    GL.Vertex2(Math.Cos(degInRad) * 80, Math.Sin(degInRad) * 80);
    GL.Vertex2(Math.Cos(degInRad) * 85, Math.Sin(degInRad) * 85);

    i = i - 6;
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    GL.End();
}

Here the static variable is subtracted for rotating clockwise and subtracted by 6 because 360/6 is equal to 60, which we need to round the circle.

So the seconds line is rotating now:

Image 6

Points of interest

For C#, I am sure that you can't find anything better than OpenTK to replace OpenGL. But if you want to develop a really professional level game, I have to tell you that you are not in the right track. C++ is far better than C# for that. And if you directly want to do that in C#, use DirectX or XNA.

History

  • 23 December, 2010: Initial post.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)