C# Programming > Drawing GDI+

C# 3D Drawing with GDI+
Euler Rotation

3D in C#

When it comes to working with 3D graphics with C# the best and most powerful tool to use would be
DirectX.

Microsoft’s GDI+ would be a poor choice since it comes with no 3D support
whatsoever. However, what’s to say we can’t try to use GDI+ to draw some basic 3D shapes?

For simple 3D plotting in a C# Form or PictureBox using DirectX would be too much. So the basic
gist of what we need to do to plot 3D points with GDI+ is to:

  • Create a Point3D class to hold X, Y, and Z values
  • Handle all the math for 3D transformations with those Point3D’s
  • Set up a Camera class to establish the point of view
  • Use some math to a Point3D into a regular Point

To demonstrate the procedures we are going to create a cube and rotate it in all directions...

3D Points to 2D Points

There is no direct way to convert a point in 3D space into a 2D point simply because that does
not make any sense. In order to convert between spaces we need to know from where we are
looking at the 3D point. For that we’ll need two values, the camera’s Z position and the zoom.

To calculate the zoom I found using the monitor’s width gives a pretty “square” 3D drawing.
(Less distortion in other words)

double zoom = (double)Screen.PrimaryScreen.Bounds.Width / 1.5;

So with the zoom we can now calculate the camera’s Z position. For this example, I use the
cube’s top right point and the cube’s center as a reference in order the keep the camera at the
same relative distance. That prevents the cube from looking like it is getting smaller and bigger
when it rotates.

Math3D.Point3D anchorPoint = (Math3D.Point3D)cubePoints[4]; //anchor point
double cameraZ = -(((anchorPoint.X - cubeOrigin.X) * zoom) / cubeOrigin.X) +
anchorPoint.Z; Math3D.Camera camera1 = new Math3D.Camera();
camera1.Position = new Math3D.Point3D(cubeOrigin.X, cubeOrigin.Y, cameraZ);

Now, with perspective values, we are ready to take our 3D points and get a simple (X, Y) value
out of them. Cold hard math here, if you can understand it that is good, if not just go with it.

//Assume
//----------
//point3D is a Point3D object with values X, Y, and Z
//camera1 is a Camera object with a Point3D Position value
//drawOrigin is a Point that represents the center of where the cube will be drawn
//cubeOrigin is the middle of the cube in 3D spaces (width / 2, height / 2, depth / 2)

Point point2D = new Point();
if (point3D.Z - camera1.Position.Z >= 0)
{
point2D.X = (int)((double)-(point3D.X - camera1.Position.X) / (-0.1f) *
zoom) + drawOrigin.X;
point2D.Y = (int)((double)(point3D.Y - camera1.Position.Y) / (-0.1f) *
zoom) + drawOrigin.Y;
}
else
{
Point tmpOrigin = new Point();
tmpOrigin.X = (int)((double)(cubeOrigin.X - camera1.Position.X) /
(double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.X;
tmpOrigin.Y = (int)((double)-(cubeOrigin.Y - camera1.Position.Y) /
(double)(cubeOrigin.Z - camera1.Position.Z) * zoom) + drawOrigin.Y;
float x = (float)((vec.X - camera1.Position.X) / (point3D.Z -
camera1.Position.Z) * zoom + drawOrigin.X);
float y = (float)(-(vec.Y - camera1.Position.Y) / (point3D.Z -
camera1.Position.Z) * zoom + drawOrigin.Y);
point2D.X = (int)x;
point2D.Y = (int)y;

}

Drawing the Cube

The basis for drawing in 3D in C# is set, so now we can use it to create a Cube C# class. The cube will have width,
height and depth and will thus have 6 faces.

The starting cube can simply be defined by creating an array for 24 totals points and then defined like so:

Math3D.Point3D[] verts = new Math3D.Point3D[24];
//front face
verts[0] = new Math3D.Point3D(0, 0, 0);
verts[1] = new Math3D.Point3D(0, height, 0);
verts[2] = new Math3D.Point3D(width, height, 0);
verts[3] = new Math3D.Point3D(width, 0, 0);
//back face
verts[4] = new Math3D.Point3D(0, 0, depth);
verts[5] = new Math3D.Point3D(0, height, depth);
verts[6] = new Math3D.Point3D(width, height, depth);
verts[7] = new Math3D.Point3D(width, 0, depth);
//etc... (rest of code can be downloaded)

We will use this array of points later when we want to rotate the cube, but for now they are ready to be
drawn. The drawing has no real trick to it; the lines just have to be drawn in the correct order:

//Back Face
g.DrawLine(Pens.Black, point3D[0], point3D[1]);
g.DrawLine(Pens.Black, point3D[1], point3D[2]);
g.DrawLine(Pens.Black, point3D[2], point3D[3]);
g.DrawLine(Pens.Black, point3D[3], point3D[0]);
//Front Face
g.DrawLine(Pens.Black, point3D[4], point3D[5]);
g.DrawLine(Pens.Black, point3D[5], point3D[6]);
g.DrawLine(Pens.Black, point3D[6], point3D[7]);
g.DrawLine(Pens.Black, point3D[7], point3D[4]);
//etc...

Alas we have a cube in C#! By the way, g is a Graphics object from wherever you want, be the Form,
PictureBox, or like in my example a Bitmap.

Rotating the Cube

To rotate the cube we’ll employ the simplest form of 3D rotation: Euler rotation. For those unfamiliar
with Euler rotation, the idea is to basically turn the X, Y, and Z values of a 3D point into a matrix like

[ x ]
[ y ]
[ z ]

And multiply that by one of 3 matrices depending about which axis we are rotating
For x-axis rotation we have the matrix:

[ 1 0 0 ]
[ 0 cos(x) sin(x)]
[ 0 -sin(x) cos(x)]

For y-axis:
[ cos(x) 0 sin(x)]
[ 0 1 0 ]
[-sin(x) 0 cos(x)]

And for z-axis:
[ cos(x) sin(x) 0]
[ -sin(x) cos(x) 0]
[ 0 0 1]

The value x being the degree of rotation. So for example, we could multiple matrixX times
matrixOurValues and the resulting matrix will have the rotated X, Y, and Z values. Easy huh?

For those of us who can’t remember how to multiply matrices together, we are in luck, I looked it up
and wrote out the three C# functions (the actual code is in the download):

public static Point3D RotateX(Point3D point3D, double degrees)
public
static Point3D RotateY(Point3D point3D, double degrees)
public
static Point3D RotateZ(Point3D point3D, double degrees)

The only thing we need to do for rotating the cube now is to loop through that array of 24 points and
rotate each of the points the same number of degrees. The only tricky part is that we want to first
translate (move) all the points so that the cube’s origin is at (0, 0, 0), then we rotate, then translate back
to the original position. That makes it so that the cube will stay in place as it rotates.

Gimbal Lock

Some math-savvy people will quickly see there is a fatal flaw to this kind of rotation. Matrix
multiplication is not like normal multiplication where A * B = B * A. In multiplying matrices, A * B is not
the same thing as B * A. In other words applying different degrees of X, Y, and Z rotations in different
orders will make the outcome different.

This problem is referred to as Gimbal Lock, where we rotate one way, then want to rotate on an axis but
the resulting rotation seems to be on a different axis.

For a way to avoid Gimbal Lock visit the improved 3D Graphics Visual C# Kicks article

Conclusion

Take a look at the attached C# project and application to see how it all comes together. You will also see
that despite the intense mathematics the program runs quite smoothly.

Once the math and the basics are in place, doing some basic 3D plotting with GDI+ becomes relatively
easy. As I mentioned before, once you want to get into more complicated 3D shapes and movements
then you’ll have to take a deep breath and integrate DirectX with C#...

Back to C# Article List