Preface
Have you ever want to do some graphics programming but think to yourself: Hey, do I have other options than DirectX/Flash/Silverlight? Fear not,
OpenGL (Open Graphics Library) is an option you've had since the 90's. Based on research I'd done in regards to generating the world famous glutTeapot via Delphi in the late 90s (don't laugh too hard) it seemed to be a great idea to port over some old school code into C#.
The install
As it's an open source product there are quite a few options to pull from. For this blog I'm using OpenTK as it was the least hassle install/code conversion over than the other options out there. This one's a pretty easy install compared to some of the topics I've blogged about in the past. Click the install below and include the 3 dlls in your project (OpenTK.dll, OpenTK.GLControl, and OpenTK.Compatibility.dll) after you've extracted them from the downloaded file.
Some coding (well, quite a bit)
The glut (GL Users Toolkit) isn't quite available for our purposes, but as luck would have it I found a project out there that used OpenTK and mapped out the necessary vertices to fake the teapot quite well. The original code within the Teapot class was written by Mark J. Kilgard in 1994. Below I've created a console application to make use of some gaming capability we have able to us via Visual Studio, plus it looked pretty good in the window :). Rather than giving a detailed explanation outside of the source code it'll most likely be easier to follow the line by line documentation I did within my project below.
using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
namespace OpenGLConsoleApp
{
class MyApplication
{
public static class Teapot
{
// Rim, body, lid, and bottom data must be reflected in x and
// y; handle and spout data across the y axis only.
public static int[,] patchdata = new int[,]
{
// rim
{102, 103, 104, 105, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
// body
{12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27},
{24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40},
// lid
{96, 96, 96, 96, 97, 98, 99, 100, 101, 101, 101, 101, 0, 1, 2, 3,},
{0, 1, 2, 3, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117},
// bottom
{118, 118, 118, 118, 124, 122, 119, 121, 123, 126, 125, 120, 40, 39, 38, 37},
// handle
{41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56},
{53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 28, 65, 66, 67},
// spout
{68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83},
{80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95}
};
public static float[,] cpdata =
{
{0.2f, 0, 2.7f}, {0.2f, -0.112f, 2.7f}, {0.112f, -0.2f, 2.7f}, {0,
-0.2f, 2.7f}, {1.3375f, 0, 2.53125f}, {1.3375f, -0.749f, 2.53125f},
{0.749f, -1.3375f, 2.53125f}, {0, -1.3375f, 2.53125f}, {1.4375f,
0, 2.53125f}, {1.4375f, -0.805f, 2.53125f}, {0.805f, -1.4375f,
2.53125f}, {0, -1.4375f, 2.53125f}, {1.5f, 0, 2.4f}, {1.5f, -0.84f,
2.4f}, {0.84f, -1.5f, 2.4f}, {0, -1.5f, 2.4f}, {1.75f, 0, 1.875f},
{1.75f, -0.98f, 1.875f}, {0.98f, -1.75f, 1.875f}, {0, -1.75f,
1.875f}, {2, 0, 1.35f}, {2, -1.12f, 1.35f}, {1.12f, -2, 1.35f},
{0, -2, 1.35f}, {2, 0, 0.9f}, {2, -1.12f, 0.9f}, {1.12f, -2,
0.9f}, {0, -2, 0.9f}, {-2, 0, 0.9f}, {2, 0, 0.45f}, {2, -1.12f,
0.45f}, {1.12f, -2, 0.45f}, {0, -2, 0.45f}, {1.5f, 0, 0.225f},
{1.5f, -0.84f, 0.225f}, {0.84f, -1.5f, 0.225f}, {0, -1.5f, 0.225f},
{1.5f, 0, 0.15f}, {1.5f, -0.84f, 0.15f}, {0.84f, -1.5f, 0.15f}, {0,
-1.5f, 0.15f}, {-1.6f, 0, 2.025f}, {-1.6f, -0.3f, 2.025f}, {-1.5f,
-0.3f, 2.25f}, {-1.5f, 0, 2.25f}, {-2.3f, 0, 2.025f}, {-2.3f, -0.3f,
2.025f}, {-2.5f, -0.3f, 2.25f}, {-2.5f, 0, 2.25f}, {-2.7f, 0,
2.025f}, {-2.7f, -0.3f, 2.025f}, {-3, -0.3f, 2.25f}, {-3, 0,
2.25f}, {-2.7f, 0, 1.8f}, {-2.7f, -0.3f, 1.8f}, {-3, -0.3f, 1.8f},
{-3, 0, 1.8f}, {-2.7f, 0, 1.575f}, {-2.7f, -0.3f, 1.575f}, {-3,
-0.3f, 1.35f}, {-3, 0, 1.35f}, {-2.5f, 0, 1.125f}, {-2.5f, -0.3f,
1.125f}, {-2.65f, -0.3f, 0.9375f}, {-2.65f, 0, 0.9375f}, {-2,
-0.3f, 0.9f}, {-1.9f, -0.3f, 0.6f}, {-1.9f, 0, 0.6f}, {1.7f, 0,
1.425f}, {1.7f, -0.66f, 1.425f}, {1.7f, -0.66f, 0.6f}, {1.7f, 0,
0.6f}, {2.6f, 0, 1.425f}, {2.6f, -0.66f, 1.425f}, {3.1f, -0.66f,
0.825f}, {3.1f, 0, 0.825f}, {2.3f, 0, 2.1f}, {2.3f, -0.25f, 2.1f},
{2.4f, -0.25f, 2.025f}, {2.4f, 0, 2.025f}, {2.7f, 0, 2.4f}, {2.7f,
-0.25f, 2.4f}, {3.3f, -0.25f, 2.4f}, {3.3f, 0, 2.4f}, {2.8f, 0,
2.475f}, {2.8f, -0.25f, 2.475f}, {3.525f, -0.25f, 2.49375f},
{3.525f, 0, 2.49375f}, {2.9f, 0, 2.475f}, {2.9f, -0.15f, 2.475f},
{3.45f, -0.15f, 2.5125f}, {3.45f, 0, 2.5125f}, {2.8f, 0, 2.4f},
{2.8f, -0.15f, 2.4f}, {3.2f, -0.15f, 2.4f}, {3.2f, 0, 2.4f}, {0, 0,
3.15f}, {0.8f, 0, 3.15f}, {0.8f, -0.45f, 3.15f}, {0.45f, -0.8f,
3.15f}, {0, -0.8f, 3.15f}, {0, 0, 2.85f}, {1.4f, 0, 2.4f}, {1.4f,
-0.784f, 2.4f}, {0.784f, -1.4f, 2.4f}, {0, -1.4f, 2.4f}, {0.4f, 0,
2.55f}, {0.4f, -0.224f, 2.55f}, {0.224f, -0.4f, 2.55f}, {0, -0.4f,
2.55f}, {1.3f, 0, 2.55f}, {1.3f, -0.728f, 2.55f}, {0.728f, -1.3f,
2.55f}, {0, -1.3f, 2.55f}, {1.3f, 0, 2.4f}, {1.3f, -0.728f, 2.4f},
{0.728f, -1.3f, 2.4f}, {0, -1.3f, 2.4f}, {0, 0, 0}, {1.425f,
-0.798f, 0}, {1.5f, 0, 0.075f}, {1.425f, 0, 0}, {0.798f, -1.425f,
0}, {0, -1.5f, 0.075f}, {0, -1.425f, 0}, {1.5f, -0.84f, 0.075f},
{0.84f, -1.5f, 0.075f}
};
public static float[] tex =
{
0, 0,
1, 0,
0, 1,
1, 1
};
private static void DrawTeapot(int grid, float scale, MeshMode2 type)
{
float[] p = new float[48], q = new float[48], r = new float[48], s = new float[48];
int i, j, k, l;
GL.PushAttrib(AttribMask.EnableBit | AttribMask.EvalBit);
GL.Enable(EnableCap.AutoNormal);
GL.Enable(EnableCap.Normalize);
GL.Enable(EnableCap.Map2Vertex3);
GL.Enable(EnableCap.Map2TextureCoord2);
// time for the math portion: remember augmented matrices? here's where you use them!
// prep the matrix for the data to be loaded
GL.PushMatrix();
// rotate the view
GL.Rotate(270.0f, 1.0f, 0.0f, 0.0f);
// set the size of the data
GL.Scale(0.5f * scale, 0.5f * scale, 0.5f * scale);
// move the data via X/Y/Z coordinates
GL.Translate(0.0f, 0.0f, -1.5f);
for (i = 0; i < 10; i++)
{
for (j = 0; j < 4; j++)
{
for (k = 0; k < 4; k++)
{
for (l = 0; l < 3; l++)
{
p[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + k], l];
q[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + (3 - k)], l];
if (l == 1)
q[j * 12 + k * 3 + l] *= -1.0f;
if (i < 6)
{
r[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + (3 - k)], l];
if (l == 0)
r[j * 12 + k * 3 + l] *= -1.0f;
s[j * 12 + k * 3 + l] = cpdata[patchdata[i, j * 4 + k], l];
if (l == 0)
s[j * 12 + k * 3 + l] *= -1.0f;
if (l == 1)
s[j * 12 + k * 3 + l] *= -1.0f;
}
}
}
}
// high level math for the texture coordinates
GL.Map2(MapTarget.Map2TextureCoord2, 0f, 1f, 2, 2, 0f, 1f, 4, 2, tex);
// high level math for the vertices
GL.Map2(MapTarget.Map2Vertex3, 0f, 1f, 3, 4, 0f, 1f, 12, 4, p);
// high level math for a 2 dimensional map
GL.MapGrid2(grid, 0.0, 1.0, grid, 0.0, 1.0);
// high level math to do the evaluation of the grids
GL.EvalMesh2(type, 0, grid, 0, grid);
// high level math for the vertices
GL.Map2(MapTarget.Map2Vertex3, 0, 1, 3, 4, 0, 1, 12, 4, q);
// high level math to do the evaluation of the grids
GL.EvalMesh2(type, 0, grid, 0, grid);
if (i < 6)
{
// high level math for the vertices
GL.Map2(MapTarget.Map2Vertex3, 0, 1, 3, 4, 0, 1, 12, 4, r);
// high level math to do the evaluation of the grids
GL.EvalMesh2(type, 0, grid, 0, grid);
// high level math for the vertices
GL.Map2(MapTarget.Map2Vertex3, 0, 1, 3, 4, 0, 1, 12, 4, s);
// high level math to do the evaluation of the grids
GL.EvalMesh2(type, 0, grid, 0, grid);
}
}
// release the manipulated data from the matrix
GL.PopMatrix();
// release the manipulated data from the matrix attributes
GL.PopAttrib();
}
public static void DrawSolidTeapot(float scale)
{
DrawTeapot(14, scale, MeshMode2.Fill);
}
public static void DrawWireTeapot(float scale)
{
DrawTeapot(10, scale, MeshMode2.Line);
}
public static void DrawPointTeapot(float scale)
{
DrawTeapot(10, scale, MeshMode2.Point);
}
}
private static int teapotList;
[STAThread]
public static void Main()
{
using (var game = new GameWindow())
{
game.Load += (sender, e) =>
{
// setup settings, load textures, sounds
game.VSync = VSyncMode.On;
// easier to create float arrays in advance
float[] ambient = { 0.0f, 0.0f, 0.0f, 1.0f };
float[] diffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
float[] position = { 0.0f, 3.0f, 3.0f, 0.0f };
float[] lmodel_ambient = { 0.2f, 0.2f, 0.2f, 1.0f };
float[] local_view = { 0.0f };
// setup your light source(s)
GL.Light(LightName.Light0, LightParameter.Ambient, ambient);
GL.Light(LightName.Light0, LightParameter.Diffuse, diffuse);
GL.Light(LightName.Light0, LightParameter.Position, position);
GL.LightModel(LightModelParameter.LightModelAmbient, lmodel_ambient);
GL.LightModel(LightModelParameter.LightModelLocalViewer, local_view);
GL.FrontFace(FrontFaceDirection.Cw);
GL.Enable(EnableCap.Lighting);
GL.Enable(EnableCap.Light0);
GL.Enable(EnableCap.AutoNormal);
GL.Enable(EnableCap.Normalize);
GL.Enable(EnableCap.DepthTest);
GL.NewList(GL.GenLists(1), ListMode.Compile);
// teapot time
Teapot.DrawSolidTeapot(1.0f);
GL.EndList();
};
game.Resize += (sender, e) =>
{
// setup the viewer for your image(s)
GL.Viewport(0, 0, game.Width, game.Height);
};
game.UpdateFrame += (sender, e) =>
{
// add game logic, input handling
if (game.Keyboard[Key.Escape])
{
game.Exit();
}
};
game.RenderFrame += (sender, e) =>
{
// step 1: clear the buffer
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
// step 2: render the teapot as you see fit
// press S for a solid teapot
if (game.Keyboard[Key.S])
Teapot.DrawSolidTeapot(0.5f);
// press W for a wire frame teapot
else if (game.Keyboard[Key.W])
Teapot.DrawWireTeapot(0.5f);
// press P for a point frame teapot
else if (game.Keyboard[Key.P])
Teapot.DrawPointTeapot(0.5f);
// step 3: force the execution of your GL code
GL.Flush();
// step 4: swap the buffers to display your code
game.SwapBuffers();
};
// Run the game at 60 updates per second
game.Run(60.0);
}
}
}
}
There's decent documentation within the above source code, the output below:
Solid:
Wire:
Pixel:
Now for some real fun: code some buttons so when you hit certain keys you change the viewing angle so you can rotate the teapot! Heck, figure out how to change the colors of the teapot on the fly so it looks like a magical teapot. Enjoy!
Source(s):