Euler Rotation and 3D Graphics


Introduction

When it comes to working with 3D graphics, 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 Form or PictureBox using DirectX would be too much. So the basic gist of what we need to do is to plot 3D points with GDI+:

  • 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 is set, so now we can use it to create a Cube 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]);

 

//Right Face

g.DrawLine(Pens.Black, point3D[8], point3D[9]);

g.DrawLine(Pens.Black, point3D[9], point3D[10]);

g.DrawLine(Pens.Black, point3D[10], point3D[11]);

g.DrawLine(Pens.Black, point3D[11], point3D[8]);

 

//Left Face

g.DrawLine(Pens.Black, point3D[12], point3D[13]);

g.DrawLine(Pens.Black, point3D[13], point3D[14]);

g.DrawLine(Pens.Black, point3D[14], point3D[15]);

g.DrawLine(Pens.Black, point3D[15], point3D[12]);

 

//Bottom Face

g.DrawLine(Pens.Black, point3D[16], point3D[17]);

g.DrawLine(Pens.Black, point3D[17], point3D[18]);

g.DrawLine(Pens.Black, point3D[18], point3D[19]);

g.DrawLine(Pens.Black, point3D[19], point3D[16]);

 

//Top Face

g.DrawLine(Pens.Black, point3D[20], point3D[21]);

g.DrawLine(Pens.Black, point3D[21], point3D[22]);

g.DrawLine(Pens.Black, point3D[22], point3D[23]);

g.DrawLine(Pens.Black, point3D[23], point3D[20]);


Alas we have a cube! 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.

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 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.

Unfortunately this problem cannot be truly overcome using Euler rotation. If it is something simple you can try using if statements to determine the order in which RotateX, RotateY, and RotateZ should be applied. Otherwise I recommend reading into Quaternions.

Conclusion

Take a look at the attached 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.

Image Rotation

As a quick note I'd like to mention that these techniques can be theoretically used to rotate pictures in 3D space as well. The missing piece there is to find the appropriate distortion algorithms once all the points in the image have been converted. Why do that? Perhaps you want to add picture faces to your cube.


Similar Articles